@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,2278 +0,0 @@
1
- //
2
- // OTPVerificationVC.swift
3
- // EasyPay
4
- //
5
- // Created by Mony's Mac on 14/08/24.
6
- //
7
-
8
- import UIKit
9
-
10
- class OTPVerificationVC: BaseVC {
11
-
12
- @IBOutlet weak var viewTextOTP1: UIView!
13
- @IBOutlet weak var txtFieldOTPText1: UITextField!
14
- @IBOutlet weak var viewTextOTP2: UIView!
15
- @IBOutlet weak var txtFieldOTPText2: UITextField!
16
- @IBOutlet weak var viewTextOTP3: UIView!
17
- @IBOutlet weak var txtFieldOTPText3: UITextField!
18
- @IBOutlet weak var viewTextOTP4: UIView!
19
- @IBOutlet weak var txtFieldOTPText4: UITextField!
20
- @IBOutlet weak var viewTextOTP5: UIView!
21
- @IBOutlet weak var txtFieldOTPText5: UITextField!
22
- @IBOutlet weak var viewTextOTP6: UIView!
23
- @IBOutlet weak var txtFieldOTPText6: UITextField!
24
- @IBOutlet weak var imgEsclamationMark: UIImageView!
25
- @IBOutlet weak var lblOtpTimer: UILabel!
26
- @IBOutlet weak var lblUntillResendOtp: UILabel!
27
- @IBOutlet weak var btnResendOTP: UIButton!
28
-
29
- @IBOutlet weak var btnConfirmCode: UIButton!
30
- @IBOutlet weak var lblUsedSavedInfo: UILabel!
31
- @IBOutlet weak var lblEnterOTP: UILabel!
32
-
33
- var cardNumber: String?
34
- var expiryDate: String?
35
- var cvv: String?
36
- var nameOnCard: String?
37
- var billingInfoData: Data?
38
-
39
- var email: String?
40
-
41
- var selectedPaymentMethod: String?
42
-
43
- //Banking Params
44
- var accountName: String?
45
- var routingNumber: String?
46
- var accountType: String?
47
- var accountNumber: String?
48
-
49
- var easyPayDelegate: EasyPayViewControllerDelegate?
50
-
51
- var easyPayVC: EasyPayViewController?
52
-
53
- var isSavedForFuture: Bool = false
54
-
55
- private var timeRemaining = 60 // 3 minutes
56
- private var timer: Timer?
57
-
58
- //GrailPay Params
59
- var grailPayAccountID: String?
60
- var selectedGrailPayAccountType: String?
61
- var selectedGrailPayAccountName: String?
62
-
63
- var request: Request!
64
-
65
- var chosenPlan: String?
66
- var startDate: String?
67
-
68
- var userEmail: String?
69
-
70
- var fieldSection: FieldSection?
71
- var additionalInfo: [FieldItem]?
72
- var billingInfo: [FieldItem]?
73
- var visibility: FieldsVisibility?
74
-
75
- // var amount: Int?
76
- var amount: Double?
77
-
78
- var isSavedNewCard: Bool = false
79
-
80
- var isSavedNewAccount: Bool?
81
- var isFrom = String()
82
-
83
- override func viewDidLoad() {
84
- super.viewDidLoad()
85
- // emailVerificationApi()
86
-
87
- uiFinishingTouchElements()
88
-
89
- // Add tap gesture recognizer to dismiss the keyboard
90
- let tapGesture = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
91
- view.addGestureRecognizer(tapGesture)
92
-
93
- setupTextFields()
94
-
95
- btnResendOTP.isHidden = true
96
-
97
- print(fieldSection ?? "")
98
- print(additionalInfo ?? "")
99
- print(billingInfo ?? "")
100
- print(visibility ?? "")
101
-
102
- // Decode request.billingInfoData
103
- if let billingData = request?.fields {
104
- do {
105
- let fieldSection = try JSONDecoder().decode(FieldSection.self, from: billingData)
106
- self.visibility = fieldSection.visibility
107
- self.billingInfo = fieldSection.billing
108
- self.additionalInfo = fieldSection.additional
109
- } catch {
110
- print("Failed to decode billing info: \(error)")
111
- }
112
- }
113
-
114
- decodeBillingInfo()
115
- }
116
-
117
- override func viewWillAppear(_ animated: Bool) {
118
- emailVerificationApi()
119
- startTimer()
120
- uiFinishingTouchElements()
121
- }
122
-
123
- // MARK: - Decode billingInfoData
124
- private func decodeBillingInfo() {
125
- guard let data = billingInfoData else {
126
- print("No billingInfoData found")
127
- return
128
- }
129
-
130
- do {
131
- let decodedSection = try JSONDecoder().decode(FieldSection.self, from: data)
132
- self.fieldSection = decodedSection
133
- self.billingInfo = decodedSection.billing
134
- self.additionalInfo = decodedSection.additional
135
- self.visibility = decodedSection.visibility
136
-
137
- print("Decoded billingInfo: \(billingInfo ?? [])")
138
- } catch {
139
- print("Failed to decode billing info data: \(error)")
140
- }
141
- }
142
-
143
- //MARK: - Ui Colors Setup.
144
- func uiFinishingTouchElements() {
145
- if let containerBGcolor = UserStoreSingleton.shared.container_bg_col,
146
- let uiColor = UIColor(hex: containerBGcolor) {
147
- self.view.backgroundColor = uiColor
148
- }
149
-
150
- if let primaryFontColor = UserStoreSingleton.shared.primary_font_col,
151
- let uiColor = UIColor(hex: primaryFontColor) {
152
- lblUsedSavedInfo.textColor = uiColor
153
- txtFieldOTPText1.textColor = uiColor
154
- txtFieldOTPText2.textColor = uiColor
155
- txtFieldOTPText3.textColor = uiColor
156
- txtFieldOTPText4.textColor = uiColor
157
- txtFieldOTPText5.textColor = uiColor
158
- txtFieldOTPText6.textColor = uiColor
159
- }
160
-
161
- if let primaryFontColor = UserStoreSingleton.shared.secondary_font_col,
162
- let uiColor = UIColor(hex: primaryFontColor) {
163
- lblEnterOTP.textColor = uiColor
164
- lblUntillResendOtp.textColor = uiColor
165
- }
166
-
167
- if let primaryFontColor = UserStoreSingleton.shared.primary_btn_bg_col,
168
- let uiColor = UIColor(hex: primaryFontColor) {
169
- btnConfirmCode.backgroundColor = uiColor
170
- btnResendOTP.tintColor = uiColor
171
- txtFieldOTPText1.tintColor = uiColor
172
- txtFieldOTPText2.tintColor = uiColor
173
- txtFieldOTPText3.tintColor = uiColor
174
- txtFieldOTPText4.tintColor = uiColor
175
- txtFieldOTPText5.tintColor = uiColor
176
- txtFieldOTPText6.tintColor = uiColor
177
- }
178
-
179
- if let secondaryBtnBackgroundColor = UserStoreSingleton.shared.primary_btn_font_col,
180
- let secondaryUIColor = UIColor(hex: secondaryBtnBackgroundColor) {
181
- btnConfirmCode.setTitleColor(secondaryUIColor, for: .normal)
182
- }
183
-
184
- if let borderRadiusString = UserStoreSingleton.shared.border_radious,
185
- let borderRadius = Double(borderRadiusString) { // Convert String to Double
186
- btnConfirmCode.layer.cornerRadius = CGFloat(borderRadius) // Set corner radius
187
- viewTextOTP1.layer.cornerRadius = CGFloat(borderRadius)
188
- viewTextOTP2.layer.cornerRadius = CGFloat(borderRadius)
189
- viewTextOTP3.layer.cornerRadius = CGFloat(borderRadius)
190
- viewTextOTP4.layer.cornerRadius = CGFloat(borderRadius)
191
- viewTextOTP5.layer.cornerRadius = CGFloat(borderRadius)
192
- viewTextOTP6.layer.cornerRadius = CGFloat(borderRadius)
193
- } else {
194
- btnConfirmCode.layer.cornerRadius = 8 // Default value
195
- }
196
- btnConfirmCode.layer.masksToBounds = true // Ensure the corners are clipped properly
197
-
198
- if let viewOtpPrimaryBackgroundColor = UserStoreSingleton.shared.primary_btn_bg_col,
199
- let primaryUIColor = UIColor(hex: viewOtpPrimaryBackgroundColor),
200
- let viewOtpSecondaryColor = UserStoreSingleton.shared.secondary_font_col,
201
- let secondaryUIColor = UIColor(hex: viewOtpSecondaryColor) {
202
-
203
- viewTextOTP1.layer.borderWidth = 1.0
204
- viewTextOTP1.layer.borderColor = txtFieldOTPText1.text?.isEmpty == false ? primaryUIColor.cgColor : secondaryUIColor.cgColor
205
- viewTextOTP2.layer.borderWidth = 1.0
206
- viewTextOTP2.layer.borderColor = txtFieldOTPText2.text?.isEmpty == false ? primaryUIColor.cgColor : secondaryUIColor.cgColor
207
- viewTextOTP3.layer.borderWidth = 1.0
208
- viewTextOTP3.layer.borderColor = txtFieldOTPText3.text?.isEmpty == false ? primaryUIColor.cgColor : secondaryUIColor.cgColor
209
- viewTextOTP4.layer.borderWidth = 1.0
210
- viewTextOTP4.layer.borderColor = txtFieldOTPText4.text?.isEmpty == false ? primaryUIColor.cgColor : secondaryUIColor.cgColor
211
- viewTextOTP5.layer.borderWidth = 1.0
212
- viewTextOTP5.layer.borderColor = txtFieldOTPText5.text?.isEmpty == false ? primaryUIColor.cgColor : secondaryUIColor.cgColor
213
- viewTextOTP6.layer.borderWidth = 1.0
214
- viewTextOTP6.layer.borderColor = txtFieldOTPText6.text?.isEmpty == false ? primaryUIColor.cgColor : secondaryUIColor.cgColor
215
- }
216
-
217
- if let fontSizeString = UserStoreSingleton.shared.fontSize,
218
- let fontSizeDouble = Double(fontSizeString) { // Convert String to Double
219
- let fontSize = CGFloat(fontSizeDouble) // Convert Double to CGFloat
220
- lblEnterOTP.font = UIFont.systemFont(ofSize: fontSize)
221
- lblOtpTimer.font = UIFont.systemFont(ofSize: fontSize)
222
- lblUntillResendOtp.font = UIFont.systemFont(ofSize: fontSize)
223
- btnResendOTP.titleLabel?.font = UIFont.systemFont(ofSize: fontSize)
224
- btnConfirmCode.titleLabel?.font = UIFont.systemFont(ofSize: fontSize)
225
-
226
- txtFieldOTPText1.font = UIFont.systemFont(ofSize: fontSize)
227
- txtFieldOTPText2.font = UIFont.systemFont(ofSize: fontSize)
228
- txtFieldOTPText3.font = UIFont.systemFont(ofSize: fontSize)
229
- txtFieldOTPText4.font = UIFont.systemFont(ofSize: fontSize)
230
- txtFieldOTPText5.font = UIFont.systemFont(ofSize: fontSize)
231
- txtFieldOTPText6.font = UIFont.systemFont(ofSize: fontSize)
232
- }
233
- }
234
-
235
- // Method to dismiss the keyboard
236
- @objc func dismissKeyboard() {
237
- view.endEditing(true)
238
- }
239
-
240
- private func startTimer() {
241
- timeRemaining = 60 // 3 minutes
242
- lblOtpTimer.text = formatTime(timeRemaining)
243
- timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(updateTimer), userInfo: nil, repeats: true)
244
- }
245
-
246
- private func stopTimer() {
247
- timer?.invalidate()
248
- timer = nil
249
- }
250
-
251
- @objc private func updateTimer() {
252
- if timeRemaining > 0 {
253
- timeRemaining -= 1
254
- lblOtpTimer.text = formatTime(timeRemaining)
255
- } else {
256
- stopTimer()
257
- btnResendOTP.isHidden = false
258
- imgEsclamationMark.isHidden = true
259
- lblOtpTimer.isHidden = true
260
- lblUntillResendOtp.isHidden = true
261
- }
262
- }
263
-
264
- private func formatTime(_ seconds: Int) -> String {
265
- let minutes = seconds / 60
266
- let seconds = seconds % 60
267
- return String(format: "%02d:%02d", minutes, seconds)
268
- }
269
-
270
- private func setupTextFields() {
271
- let textFields = [txtFieldOTPText1, txtFieldOTPText2, txtFieldOTPText3, txtFieldOTPText4, txtFieldOTPText5, txtFieldOTPText6]
272
-
273
- for textField in textFields {
274
- textField?.delegate = self
275
- textField?.textContentType = .oneTimeCode
276
- textField?.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged)
277
- }
278
- }
279
-
280
- //MARK: - Send OTP Email Verification Api
281
- func emailVerificationApi() {
282
- showLoadingIndicator()
283
-
284
- let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.emailVerification.path()
285
-
286
- guard let serviceURL = URL(string: fullURL) else {
287
- print("Invalid URL")
288
- hideLoadingIndicator()
289
- return
290
- }
291
-
292
- var request = URLRequest(url: serviceURL)
293
- request.httpMethod = "POST"
294
- request.addValue("application/json", forHTTPHeaderField: "Content-Type")
295
-
296
- let token = UserStoreSingleton.shared.clientToken
297
- print("Setting clientToken header: \(token ?? "None")")
298
- request.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
299
-
300
- let params: [String: Any] = [
301
- "card_search_value": email ?? "",
302
- "card_search_key": "email"
303
- ]
304
-
305
- do {
306
- let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
307
- request.httpBody = jsonData
308
- if let jsonString = String(data: jsonData, encoding: .utf8) {
309
- print("JSON Payload: \(jsonString)")
310
- }
311
- } catch let error {
312
- print("Error creating JSON data: \(error)")
313
- hideLoadingIndicator()
314
- return
315
- }
316
-
317
- let session = URLSession.shared
318
- let task = session.dataTask(with: request) { (serviceData, serviceResponse, error) in
319
-
320
- DispatchQueue.main.async {
321
- self.hideLoadingIndicator() // Stop loader when response is received
322
- }
323
-
324
- if let error = error {
325
- print("Error: \(error.localizedDescription)")
326
- return
327
- }
328
-
329
- guard let httpResponse = serviceResponse as? HTTPURLResponse else {
330
- print("Invalid response")
331
- return
332
- }
333
-
334
- if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
335
- if let data = serviceData {
336
- do {
337
- if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
338
- print("Response Data: \(responseObject)")
339
-
340
- } else {
341
- print("Invalid JSON format")
342
- }
343
- } catch let jsonError {
344
- print("Error parsing JSON: \(jsonError)")
345
- }
346
- } else {
347
- print("No data received")
348
- }
349
- } else {
350
- print("HTTP Status Code: \(httpResponse.statusCode)")
351
- }
352
- }
353
- task.resume()
354
- }
355
-
356
- //MARK: - OTP Verification Api
357
- func otpVerificationApi() {
358
- showLoadingIndicator()
359
-
360
- let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.verifyOtp.path()
361
-
362
- guard let serviceURL = URL(string: fullURL) else {
363
- print("Invalid URL")
364
- hideLoadingIndicator()
365
- return
366
- }
367
-
368
- var uRLRequest = URLRequest(url: serviceURL)
369
- uRLRequest.httpMethod = "POST"
370
- uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
371
-
372
- let token = UserStoreSingleton.shared.clientToken
373
- print("Setting clientToken header: \(token ?? "None")")
374
- uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
375
-
376
- let params: [String: Any] = [
377
- "card_search_value": email ?? "",
378
- "card_search_key": "email",
379
- "otp": getCombinedOTP()
380
- ]
381
-
382
- do {
383
- let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
384
- uRLRequest.httpBody = jsonData
385
- if let jsonString = String(data: jsonData, encoding: .utf8) {
386
- print("JSON Payload: \(jsonString)")
387
- }
388
- } catch let error {
389
- print("Error creating JSON data: \(error)")
390
- hideLoadingIndicator()
391
- return
392
- }
393
-
394
- let session = URLSession.shared
395
- let task = session.dataTask(with: uRLRequest) { (serviceData, serviceResponse, error) in
396
-
397
- DispatchQueue.main.async {
398
- self.hideLoadingIndicator() // Stop loader when response is received
399
- }
400
-
401
- if let error = error {
402
- print("Error: \(error.localizedDescription)")
403
- return
404
- }
405
-
406
- guard let httpResponse = serviceResponse as? HTTPURLResponse else {
407
- print("Invalid response")
408
- return
409
- }
410
-
411
- if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
412
- if let data = serviceData {
413
- do {
414
- if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
415
- print("Response Data: \(responseObject)")
416
- if self.selectedPaymentMethod == "Card"{
417
- if let dataSection = responseObject["data"] as? [String: Any],
418
- let innerData = dataSection["data"] as? [String: Any],
419
- let customerId = innerData["customer_id"] as? String {
420
- print("Extracted customer_id: \(customerId)")
421
- if self.billingInfoData == nil {
422
- if self.request.secureAuthentication == true {
423
- self.threeDSecurePaymentApi(customerId: customerId)
424
- }
425
- else {
426
- self.paymentIntentWithSavedCardApi(customerId: customerId)
427
- }
428
- }
429
- else {
430
- // self.paymentIntentApi(customerId: customerId)
431
- if let request = self.request {
432
- if request.secureAuthentication == true {
433
- self.threeDSecurePaymentSavedCardApi(customerId: customerId)
434
- } else {
435
- self.paymentIntentApi(customerId: customerId)
436
- }
437
- }
438
- }
439
- } else {
440
- print("customer_id not found in nested data. Falling back to nil.")
441
- if let request = self.request {
442
- if request.secureAuthentication == true {
443
- self.threeDSecurePaymentSavedCardApi(customerId: nil)
444
- }
445
- else {
446
- self.paymentIntentApi(customerId: nil)
447
- }
448
- }
449
- }
450
- }
451
- else if self.selectedPaymentMethod == "Bank" {
452
-
453
- if let dataSection = responseObject["data"] as? [String: Any],
454
- let innerData = dataSection["data"] as? [String: Any],
455
- let customerId = innerData["customer_id"] as? String {
456
- print("Extracted customer_id: \(customerId)")
457
- if self.billingInfoData == nil {
458
- self.accountChargeWithSavedAccountApi(customerId: customerId)
459
- }
460
- else {
461
- self.accountChargeApi(customerId: customerId)
462
- }
463
- } else {
464
- print("customer_id not found in nested data. Falling back to nil.")
465
- if self.billingInfoData == nil {
466
- self.accountChargeWithSavedAccountApi(customerId: nil)
467
- }
468
- else {
469
- self.accountChargeApi(customerId: nil)
470
- }
471
- }
472
- }
473
- else if self.selectedPaymentMethod == "GrailPay" {
474
- if let dataSection = responseObject["data"] as? [String: Any],
475
- let innerData = dataSection["data"] as? [String: Any],
476
- let customerId = innerData["customer_id"] as? String {
477
- print("Extracted customer_id: \(customerId)")
478
- self.grailPayAccountChargeApi(customerId: customerId)
479
- } else {
480
- print("customer_id not found. Sending empty customerId.")
481
- self.grailPayAccountChargeApi(customerId: nil)
482
- }
483
- }
484
- } else {
485
- print("Invalid JSON format")
486
- }
487
- } catch let jsonError {
488
- print("Error parsing JSON: \(jsonError)")
489
- }
490
- } else {
491
- print("No data received")
492
- }
493
- } else {
494
- print("HTTP Status Code: \(httpResponse.statusCode)")
495
- if let data = serviceData,
496
- let responseObj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
497
- let message = responseObj["message"] as? String {
498
- DispatchQueue.main.async {
499
- self.showToast(message: message)
500
- }
501
- }
502
- }
503
- }
504
- task.resume()
505
- }
506
-
507
- //MARK: - Card Charge Api
508
- func paymentIntentApi(customerId: String?) {
509
- showLoadingIndicator()
510
-
511
- let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.charges.path()
512
-
513
- guard let serviceURL = URL(string: fullURL) else {
514
- print("Invalid URL")
515
- hideLoadingIndicator()
516
- return
517
- }
518
-
519
- var uRLRequest = URLRequest(url: serviceURL)
520
- uRLRequest.httpMethod = "POST"
521
- uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
522
-
523
- let token = UserStoreSingleton.shared.clientToken
524
- print("Setting clientToken header: \(token ?? "None")")
525
- uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
526
-
527
- let emailPrefix = userEmail?.components(separatedBy: "@").first ?? ""
528
-
529
- var params: [String: Any] = [
530
- "name": nameOnCard ?? "",
531
- "card_number": cardNumber?.replacingOccurrences(of: " ", with: "") ?? "",
532
- "cardholder_name": nameOnCard ?? "",
533
- "exp_month": expiryDate?.components(separatedBy: "/").first ?? "",
534
- "exp_year": expiryDate?.components(separatedBy: "/").last ?? "",
535
- "cvc": cvv ?? "",
536
- "currency": "usd",
537
- "payment_method": selectedPaymentMethod ?? "",
538
- "save_card": isSavedNewCard ? 1 : 0,
539
- // "create_customer": "1"
540
- "email": userEmail ?? "",
541
- // "price":"6.0",
542
- "tokenize": request.tokenOnly ?? "",
543
- "username": emailPrefix
544
- ]
545
-
546
- // Add is_default parameter if save_card is 1
547
- if isSavedNewCard {
548
- params["is_default"] = "1"
549
- }
550
-
551
- if let customerId = customerId {
552
- params["customer"] = customerId
553
- params["customer_id"] = customerId
554
- }
555
- // else {
556
- // params["username"] = emailPrefix
557
- // params["email"] = userEmail ?? ""
558
- // }
559
-
560
- if customerId == nil {
561
- params["create_customer"] = "1"
562
- }
563
-
564
- if let billingInfoData = request.fields {
565
- do {
566
- let fieldSection = try JSONDecoder().decode(FieldSection.self, from: billingInfoData)
567
-
568
- // Billing Info
569
- let billing = fieldSection.billing
570
- if !billing.isEmpty {
571
- var billingDict: [String: Any] = [:]
572
- billing.forEach { billingDict[$0.name] = $0.value }
573
-
574
- if let address = billingDict["address"] as? String, !address.isEmpty {
575
- params["address"] = address
576
- }
577
- if let country = billingDict["country"] as? String, !country.isEmpty {
578
- params["country"] = country
579
- }
580
- if let state = billingDict["state"] as? String, !state.isEmpty {
581
- params["state"] = state
582
- }
583
- if let city = billingDict["city"] as? String, !city.isEmpty {
584
- params["city"] = city
585
- }
586
- if let postalCode = billingDict["postal_code"] as? String, !postalCode.isEmpty {
587
- params["zip"] = postalCode
588
- }
589
- }
590
-
591
- // Additional Info
592
- let additional = fieldSection.additional
593
- if !additional.isEmpty {
594
- var additionalDict: [String: Any] = [:]
595
- additional.forEach { additionalDict[$0.name] = $0.value }
596
-
597
- if let desc = additionalDict["description"] as? String, !desc.isEmpty {
598
- params["description"] = desc
599
- } else {
600
- params["description"] = "Hosted payment checkout"
601
- }
602
-
603
- if let phone = additionalDict["phone_number"] as? String, !phone.isEmpty {
604
- params["phone_number"] = phone
605
- }
606
- if let email = additionalDict["email"] as? String, !email.isEmpty {
607
- params["email"] = email
608
- }
609
- if let name = additionalDict["name"] as? String, !name.isEmpty {
610
- params["name"] = name
611
- }
612
- } else {
613
- // If no description in additional info, set default
614
- params["description"] = "Hosted payment checkout"
615
- }
616
-
617
- } catch {
618
- print("Failed to decode FieldSection: \(error)")
619
- params["description"] = "Hosted payment checkout"
620
- }
621
- } else {
622
- // Fallback if billingInfoData is missing
623
- params["description"] = "Hosted payment checkout"
624
- }
625
-
626
- // Add these if recurring is enabled
627
- // if let req = request, req.is_recurring == true {
628
- // if let recurringType = req.recurringStartDateType, recurringType == .custom {
629
- // // Only send start_date if type is .custom and field is not empty
630
- // if let startDateText = startDate, !startDateText.isEmpty {
631
- // let inputFormatter = DateFormatter()
632
- // inputFormatter.dateFormat = "dd/MM/yyyy"
633
- //
634
- // let outputFormatter = DateFormatter()
635
- // outputFormatter.dateFormat = "MM/dd/yyyy"
636
- //
637
- // if let date = inputFormatter.date(from: startDateText) {
638
- // let apiFormattedDate = outputFormatter.string(from: date)
639
- // params["start_date"] = apiFormattedDate
640
- // } else {
641
- // print("Invalid date format in startDateText")
642
- // }
643
- // }
644
- // }
645
- //
646
- // params["interval"] = chosenPlan?.lowercased()
647
- // }
648
-
649
- // Add these if recurring is enabled
650
- if let req = request, req.is_recurring == true {
651
- if let startDateText = startDate, !startDateText.isEmpty {
652
- let inputFormatter = DateFormatter()
653
- inputFormatter.dateFormat = "dd/MM/yyyy"
654
-
655
- let outputFormatter = DateFormatter()
656
- outputFormatter.dateFormat = "MM/dd/yyyy"
657
-
658
- if let date = inputFormatter.date(from: startDateText) {
659
- let apiFormattedDate = outputFormatter.string(from: date)
660
- params["start_date"] = apiFormattedDate
661
- } else {
662
- print("Invalid date format in startDateText")
663
- }
664
- }
665
-
666
- // interval is still required
667
- params["interval"] = chosenPlan?.lowercased()
668
- }
669
-
670
- // ✅ Include metadata only if it has at least 1 key-value pair
671
- if let metadata = request?.metadata, !metadata.isEmpty {
672
- params["metadata"] = metadata
673
- }
674
-
675
- print(params)
676
-
677
- do {
678
- let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
679
- uRLRequest.httpBody = jsonData
680
- if let jsonString = String(data: jsonData, encoding: .utf8) {
681
- print("JSON Payload: \(jsonString)")
682
- }
683
- } catch let error {
684
- print("Error creating JSON data: \(error)")
685
- hideLoadingIndicator()
686
- return
687
- }
688
-
689
- let session = URLSession.shared
690
- let task = session.dataTask(with: uRLRequest) { (serviceData, serviceResponse, error) in
691
-
692
- DispatchQueue.main.async {
693
- self.hideLoadingIndicator() // Stop loader when response is received
694
- }
695
-
696
- if let error = error {
697
- self.presentPaymentErrorVC(errorMessage: error.localizedDescription)
698
- return
699
- }
700
-
701
- guard let httpResponse = serviceResponse as? HTTPURLResponse else {
702
- self.presentPaymentErrorVC(errorMessage: "Invalid response")
703
- return
704
- }
705
-
706
- if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
707
- if let data = serviceData {
708
- do {
709
- if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
710
- print("Response Data: \(responseObject)")
711
-
712
- // ✅ Handle duplicate transaction case
713
- if let status = responseObject["status"] as? Bool, status == false,
714
- let message = responseObject["message"] as? String,
715
- message.lowercased().contains("duplicate transaction") {
716
- self.presentPaymentErrorVC(errorMessage: message)
717
- return
718
- }
719
-
720
- if let status = responseObject["status"] as? Int, status == 0,
721
- let message = responseObject["message"] as? String,
722
- message.lowercased().contains("duplicate transaction") {
723
- self.presentPaymentErrorVC(errorMessage: message)
724
- return
725
- }
726
-
727
- // ✅ Handle generic "status == 0" error case
728
- if let status = responseObject["status"] as? Int, status == 0 {
729
- let errorMessage = responseObject["message"] as? String ?? "Unknown error"
730
- self.presentPaymentErrorVC(errorMessage: errorMessage)
731
- return
732
- }
733
- else {
734
- DispatchQueue.main.async {
735
- if let paymentDoneVC = self.storyboard?.instantiateViewController(withIdentifier: "PaymentDoneVC") as? PaymentDoneVC {
736
- paymentDoneVC.chargeData = responseObject
737
- paymentDoneVC.selectedPaymentMethod = self.selectedPaymentMethod
738
- paymentDoneVC.easyPayDelegate = self.easyPayDelegate
739
- paymentDoneVC.request = self.request
740
-
741
- // Pass billing info and additional info if available
742
- if let billingInfoData = self.request.fields,
743
- let fieldSection = try? JSONDecoder().decode(FieldSection.self, from: billingInfoData) {
744
-
745
- // Filter billing info: only include non-empty values
746
- let filteredBilling = fieldSection.billing.filter { !($0.value.trimmingCharacters(in: .whitespaces).isEmpty) }
747
- paymentDoneVC.billingInfoData = filteredBilling
748
- var billingDict: [String: Any] = [:]
749
- filteredBilling.forEach { billingDict[$0.name] = $0.value }
750
- paymentDoneVC.billingInfo = billingDict
751
-
752
- // Filter additional info: only include non-empty values
753
- let filteredAdditional = fieldSection.additional.filter { !($0.value.trimmingCharacters(in: .whitespaces).isEmpty) }
754
- paymentDoneVC.additionalInfoData = filteredAdditional
755
- var additionalDict: [String: Any] = [:]
756
- filteredAdditional.forEach { additionalDict[$0.name] = $0.value }
757
- paymentDoneVC.additionalInfo = additionalDict
758
- }
759
- self.navigationController?.pushViewController(paymentDoneVC, animated: true)
760
- }
761
- }
762
- }
763
- } else {
764
- self.presentPaymentErrorVC(errorMessage: "Invalid JSON format")
765
- }
766
- } catch let jsonError {
767
- self.presentPaymentErrorVC(errorMessage: "Error parsing JSON: \(jsonError)")
768
- }
769
- } else {
770
- self.presentPaymentErrorVC(errorMessage: "No data received")
771
- }
772
- } else {
773
- if let data = serviceData,
774
- let responseObj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
775
- let message = responseObj["message"] as? String {
776
- self.presentPaymentErrorVC(errorMessage: message)
777
- } else {
778
- self.presentPaymentErrorVC(errorMessage: "HTTP Status Code: \(httpResponse.statusCode)")
779
- }
780
- }
781
- }
782
- task.resume()
783
- }
784
-
785
- //MARK: - Card Charge Api If billing info is nil and saved card
786
- func paymentIntentWithSavedCardApi(customerId: String?) {
787
- showLoadingIndicator()
788
-
789
- let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.charges.path()
790
-
791
- guard let serviceURL = URL(string: fullURL) else {
792
- print("Invalid URL")
793
- hideLoadingIndicator()
794
- return
795
- }
796
-
797
- var urlRequest = URLRequest(url: serviceURL)
798
- urlRequest.httpMethod = "POST"
799
- urlRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
800
-
801
- let token = UserStoreSingleton.shared.clientToken
802
- print("Setting clientToken header: \(token ?? "None")")
803
- urlRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
804
-
805
- let emailPrefix = email?.components(separatedBy: "@").first ?? ""
806
-
807
- var params: [String: Any] = [
808
- "name": emailPrefix,
809
- "card_number": cardNumber?.replacingOccurrences(of: " ", with: "") ?? "",
810
- "cardholder_name": nameOnCard ?? "",
811
- "exp_month": expiryDate?.components(separatedBy: "/").first ?? "",
812
- "exp_year": expiryDate?.components(separatedBy: "/").last ?? "",
813
- "cvc": cvv ?? "",
814
- "description": "description",
815
- "currency": "usd",
816
- "payment_method": selectedPaymentMethod ?? "",
817
- "save_card": isSavedNewCard ? 1 : 0
818
- ]
819
-
820
- // Add is_default parameter if save_card is 1
821
- if isSavedNewCard {
822
- params["is_default"] = "1"
823
- }
824
-
825
- if let customerId = customerId {
826
- params["customer"] = customerId
827
- params["customer_id"] = customerId
828
- } else {
829
- params["username"] = emailPrefix
830
- params["email"] = email ?? ""
831
- }
832
-
833
- if customerId == nil {
834
- params["create_customer"] = "1"
835
- }
836
-
837
- // Add these if recurring is enabled
838
- // if let req = request, req.is_recurring == true {
839
- // if let recurringType = req.recurringStartDateType, recurringType == .custom {
840
- // // Only send start_date if type is .custom and field is not empty
841
- // if let startDateText = startDate, !startDateText.isEmpty {
842
- // let inputFormatter = DateFormatter()
843
- // inputFormatter.dateFormat = "dd/MM/yyyy"
844
- //
845
- // let outputFormatter = DateFormatter()
846
- // outputFormatter.dateFormat = "MM/dd/yyyy"
847
- //
848
- // if let date = inputFormatter.date(from: startDateText) {
849
- // let apiFormattedDate = outputFormatter.string(from: date)
850
- // params["start_date"] = apiFormattedDate
851
- // } else {
852
- // print("Invalid date format in startDateText")
853
- // }
854
- // }
855
- // }
856
- //
857
- // params["interval"] = chosenPlan?.lowercased()
858
- // }
859
-
860
- // Add these if recurring is enabled
861
- if let req = request, req.is_recurring == true {
862
- if let startDateText = startDate, !startDateText.isEmpty {
863
- let inputFormatter = DateFormatter()
864
- inputFormatter.dateFormat = "dd/MM/yyyy"
865
-
866
- let outputFormatter = DateFormatter()
867
- outputFormatter.dateFormat = "MM/dd/yyyy"
868
-
869
- if let date = inputFormatter.date(from: startDateText) {
870
- let apiFormattedDate = outputFormatter.string(from: date)
871
- params["start_date"] = apiFormattedDate
872
- } else {
873
- print("Invalid date format in startDateText")
874
- }
875
- }
876
-
877
- // interval is still required
878
- params["interval"] = chosenPlan?.lowercased()
879
- }
880
-
881
- // ✅ Include metadata only if it has at least 1 key-value pair
882
- if let metadata = request?.metadata, !metadata.isEmpty {
883
- params["metadata"] = metadata
884
- }
885
-
886
- print(params)
887
-
888
- do {
889
- let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
890
- urlRequest.httpBody = jsonData
891
- if let jsonString = String(data: jsonData, encoding: .utf8) {
892
- print("JSON Payload: \(jsonString)")
893
- }
894
- } catch let error {
895
- print("Error creating JSON data: \(error)")
896
- hideLoadingIndicator()
897
- return
898
- }
899
-
900
- let session = URLSession.shared
901
- let task = session.dataTask(with: urlRequest) { (serviceData, serviceResponse, error) in
902
-
903
- DispatchQueue.main.async {
904
- self.hideLoadingIndicator() // Stop loader when response is received
905
- }
906
-
907
- if let error = error {
908
- self.presentPaymentErrorVC(errorMessage: error.localizedDescription)
909
- return
910
- }
911
-
912
- guard let httpResponse = serviceResponse as? HTTPURLResponse else {
913
- self.presentPaymentErrorVC(errorMessage: "Invalid response")
914
- return
915
- }
916
-
917
- if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
918
- if let data = serviceData {
919
- do {
920
- if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
921
- print("Response Data: \(responseObject)")
922
-
923
- // ✅ Handle duplicate transaction case
924
- if let status = responseObject["status"] as? Bool, status == false,
925
- let message = responseObject["message"] as? String,
926
- message.lowercased().contains("duplicate transaction") {
927
- self.presentPaymentErrorVC(errorMessage: message)
928
- return
929
- }
930
-
931
- if let status = responseObject["status"] as? Int, status == 0,
932
- let message = responseObject["message"] as? String,
933
- message.lowercased().contains("duplicate transaction") {
934
- self.presentPaymentErrorVC(errorMessage: message)
935
- return
936
- }
937
-
938
- // ✅ Handle generic "status == 0" error case
939
- if let status = responseObject["status"] as? Int, status == 0 {
940
- let errorMessage = responseObject["message"] as? String ?? "Unknown error"
941
- self.presentPaymentErrorVC(errorMessage: errorMessage)
942
- return
943
- }
944
- else {
945
- DispatchQueue.main.async {
946
- if let paymentDoneVC = self.storyboard?.instantiateViewController(withIdentifier: "PaymentDoneVC") as? PaymentDoneVC {
947
- paymentDoneVC.chargeData = responseObject
948
- paymentDoneVC.selectedPaymentMethod = self.selectedPaymentMethod
949
- paymentDoneVC.easyPayDelegate = self.easyPayDelegate
950
- paymentDoneVC.request = self.request
951
- self.navigationController?.pushViewController(paymentDoneVC, animated: true)
952
- }
953
- }
954
- }
955
- } else {
956
- self.presentPaymentErrorVC(errorMessage: "Invalid JSON format")
957
- }
958
- } catch let jsonError {
959
- self.presentPaymentErrorVC(errorMessage: "Error parsing JSON: \(jsonError)")
960
- }
961
- } else {
962
- self.presentPaymentErrorVC(errorMessage: "No data received")
963
- }
964
- } else {
965
- if let data = serviceData,
966
- let responseObj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
967
- let message = responseObj["message"] as? String {
968
- self.presentPaymentErrorVC(errorMessage: message)
969
- } else {
970
- self.presentPaymentErrorVC(errorMessage: "HTTP Status Code: \(httpResponse.statusCode)")
971
- }
972
- }
973
- }
974
- task.resume()
975
- }
976
-
977
- func presentPaymentErrorVC(errorMessage: String) {
978
- DispatchQueue.main.async {
979
- if let paymentErrorVC = self.storyboard?.instantiateViewController(withIdentifier: "PaymentErrorVC") as? PaymentErrorVC {
980
- paymentErrorVC.errorMessage = errorMessage
981
- paymentErrorVC.easyPayDelegate = self.easyPayDelegate // Pass the reference here
982
- self.navigationController?.pushViewController(paymentErrorVC, animated: true)
983
- }
984
- }
985
- }
986
-
987
- //MARK: - Account Charge Api
988
- func accountChargeApi(customerId: String?) {
989
- showLoadingIndicator()
990
-
991
- let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.achCharge.path()
992
-
993
- guard let serviceURL = URL(string: fullURL) else {
994
- print("Invalid URL")
995
- hideLoadingIndicator()
996
- return
997
- }
998
-
999
- var uRLRequest = URLRequest(url: serviceURL)
1000
- uRLRequest.httpMethod = "POST"
1001
- uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
1002
-
1003
- let token = UserStoreSingleton.shared.clientToken
1004
- print("Setting clientToken header: \(token ?? "None")")
1005
- uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
1006
-
1007
- let emailPrefix = email?.components(separatedBy: "@").first ?? ""
1008
-
1009
- var params: [String: Any] = [
1010
- // "name": accountName ?? "",
1011
- "name": !(request.name?.isEmpty ?? true) ? request.name! : (accountName ?? ""),
1012
- "email": userEmail ?? "",
1013
- "currency": "usd",
1014
- "account_type": accountType?.lowercased() ?? "",
1015
- "routing_number": routingNumber ?? "",
1016
- "account_number": accountNumber ?? "",
1017
- "payment_mode": "auth_and_capture",
1018
- "levelIndicator": 1,
1019
- "save_account": (isSavedNewAccount ?? false) ? 1 : 0,
1020
- "payment_method": "ach"
1021
- ]
1022
-
1023
- if let customerId = customerId {
1024
- params["customer"] = customerId
1025
- } else {
1026
- params["username"] = emailPrefix
1027
- }
1028
-
1029
- if customerId == nil {
1030
- params["create_customer"] = "1"
1031
- }
1032
-
1033
- if let billingInfoData = request.fields {
1034
- do {
1035
- let fieldSection = try JSONDecoder().decode(FieldSection.self, from: billingInfoData)
1036
-
1037
- // Billing Info
1038
- let billing = fieldSection.billing
1039
- if !billing.isEmpty {
1040
- var billingDict: [String: Any] = [:]
1041
- billing.forEach { billingDict[$0.name] = $0.value }
1042
-
1043
- if let address = billingDict["address"] as? String, !address.isEmpty {
1044
- params["address"] = address
1045
- }
1046
- if let country = billingDict["country"] as? String, !country.isEmpty {
1047
- params["country"] = country
1048
- }
1049
- if let state = billingDict["state"] as? String, !state.isEmpty {
1050
- params["state"] = state
1051
- }
1052
- if let city = billingDict["city"] as? String, !city.isEmpty {
1053
- params["city"] = city
1054
- }
1055
- if let postalCode = billingDict["postal_code"] as? String, !postalCode.isEmpty {
1056
- params["zip"] = postalCode
1057
- }
1058
- }
1059
-
1060
- // Additional Info
1061
- let additional = fieldSection.additional
1062
- if !additional.isEmpty {
1063
- var additionalDict: [String: Any] = [:]
1064
- additional.forEach { additionalDict[$0.name] = $0.value }
1065
-
1066
- if let desc = additionalDict["description"] as? String, !desc.isEmpty {
1067
- params["description"] = desc
1068
- } else {
1069
- params["description"] = "Hosted payment checkout"
1070
- }
1071
-
1072
- if let phone = additionalDict["phone_number"] as? String, !phone.isEmpty {
1073
- params["phone_number"] = phone
1074
- }
1075
- if let email = additionalDict["email"] as? String, !email.isEmpty {
1076
- params["email"] = email
1077
- }
1078
- if let name = additionalDict["name"] as? String, !name.isEmpty {
1079
- params["name"] = name
1080
- }
1081
- } else {
1082
- // If no description in additional info, set default
1083
- params["description"] = "Hosted payment checkout"
1084
- }
1085
-
1086
- } catch {
1087
- print("Failed to decode FieldSection: \(error)")
1088
- params["description"] = "Hosted payment checkout"
1089
- }
1090
- } else {
1091
- // Fallback if billingInfoData is missing
1092
- params["description"] = "Hosted payment checkout"
1093
- }
1094
-
1095
- // Add these if recurring is enabled
1096
- // if let req = request, req.is_recurring == true {
1097
- // if let recurringType = req.recurringStartDateType, recurringType == .custom {
1098
- // // Only send start_date if type is .custom and field is not empty
1099
- // if let startDateText = startDate, !startDateText.isEmpty {
1100
- // let inputFormatter = DateFormatter()
1101
- // inputFormatter.dateFormat = "dd/MM/yyyy"
1102
- //
1103
- // let outputFormatter = DateFormatter()
1104
- // outputFormatter.dateFormat = "MM/dd/yyyy"
1105
- //
1106
- // if let date = inputFormatter.date(from: startDateText) {
1107
- // let apiFormattedDate = outputFormatter.string(from: date)
1108
- // params["start_date"] = apiFormattedDate
1109
- // } else {
1110
- // print("Invalid date format in startDateText")
1111
- // }
1112
- // }
1113
- // }
1114
- //
1115
- // params["interval"] = chosenPlan?.lowercased()
1116
- // }
1117
-
1118
- // Add these if recurring is enabled
1119
- if let req = request, req.is_recurring == true {
1120
- if let startDateText = startDate, !startDateText.isEmpty {
1121
- let inputFormatter = DateFormatter()
1122
- inputFormatter.dateFormat = "dd/MM/yyyy"
1123
-
1124
- let outputFormatter = DateFormatter()
1125
- outputFormatter.dateFormat = "MM/dd/yyyy"
1126
-
1127
- if let date = inputFormatter.date(from: startDateText) {
1128
- let apiFormattedDate = outputFormatter.string(from: date)
1129
- params["start_date"] = apiFormattedDate
1130
- } else {
1131
- print("Invalid date format in startDateText")
1132
- }
1133
- }
1134
-
1135
- // interval is still required
1136
- params["interval"] = chosenPlan?.lowercased()
1137
- }
1138
-
1139
- // ✅ Include metadata only if it has at least 1 key-value pair
1140
- if let metadata = request?.metadata, !metadata.isEmpty {
1141
- params["metadata"] = metadata
1142
- }
1143
-
1144
- print(params)
1145
-
1146
- do {
1147
- let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
1148
- uRLRequest.httpBody = jsonData
1149
- if let jsonString = String(data: jsonData, encoding: .utf8) {
1150
- print("JSON Payload: \(jsonString)")
1151
- }
1152
- } catch let error {
1153
- print("Error creating JSON data: \(error)")
1154
- hideLoadingIndicator()
1155
- return
1156
- }
1157
-
1158
- let session = URLSession.shared
1159
- let task = session.dataTask(with: uRLRequest) { (serviceData, serviceResponse, error) in
1160
-
1161
- DispatchQueue.main.async {
1162
- self.hideLoadingIndicator() // Stop loader when response is received
1163
- }
1164
-
1165
- if let error = error {
1166
- self.presentPaymentErrorVC(errorMessage: error.localizedDescription)
1167
- return
1168
- }
1169
-
1170
- guard let httpResponse = serviceResponse as? HTTPURLResponse else {
1171
- self.presentPaymentErrorVC(errorMessage: "Invalid response")
1172
- return
1173
- }
1174
-
1175
- if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
1176
- if let data = serviceData {
1177
- do {
1178
- if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
1179
- print("Response Data: \(responseObject)")
1180
-
1181
- // ✅ Handle duplicate transaction case
1182
- if let status = responseObject["status"] as? Bool, status == false,
1183
- let message = responseObject["message"] as? String,
1184
- message.lowercased().contains("duplicate transaction") {
1185
- self.presentPaymentErrorVC(errorMessage: message)
1186
- return
1187
- }
1188
-
1189
- if let status = responseObject["status"] as? Int, status == 0,
1190
- let message = responseObject["message"] as? String,
1191
- message.lowercased().contains("duplicate transaction") {
1192
- self.presentPaymentErrorVC(errorMessage: message)
1193
- return
1194
- }
1195
-
1196
- // ✅ Handle generic "status == 0" error case
1197
- if let status = responseObject["status"] as? Int, status == 0 {
1198
- let errorMessage = responseObject["message"] as? String ?? "Unknown error"
1199
- self.presentPaymentErrorVC(errorMessage: errorMessage)
1200
- return
1201
- }
1202
- else {
1203
- DispatchQueue.main.async {
1204
- if let paymentDoneVC = self.storyboard?.instantiateViewController(withIdentifier: "PaymentDoneVC") as? PaymentDoneVC {
1205
- paymentDoneVC.chargeData = responseObject
1206
- paymentDoneVC.selectedPaymentMethod = self.selectedPaymentMethod
1207
- paymentDoneVC.easyPayDelegate = self.easyPayDelegate
1208
- paymentDoneVC.bankPaymentParams = params
1209
- paymentDoneVC.request = self.request
1210
- // Pass billing info and additional info if available
1211
- if let billingInfoData = self.request.fields,
1212
- let fieldSection = try? JSONDecoder().decode(FieldSection.self, from: billingInfoData) {
1213
-
1214
- // Filter billing info: only include non-empty values
1215
- let filteredBilling = fieldSection.billing.filter { !($0.value.trimmingCharacters(in: .whitespaces).isEmpty) }
1216
- paymentDoneVC.billingInfoData = filteredBilling
1217
- var billingDict: [String: Any] = [:]
1218
- filteredBilling.forEach { billingDict[$0.name] = $0.value }
1219
- paymentDoneVC.billingInfo = billingDict
1220
-
1221
- // Filter additional info: only include non-empty values
1222
- let filteredAdditional = fieldSection.additional.filter { !($0.value.trimmingCharacters(in: .whitespaces).isEmpty) }
1223
- paymentDoneVC.additionalInfoData = filteredAdditional
1224
- var additionalDict: [String: Any] = [:]
1225
- filteredAdditional.forEach { additionalDict[$0.name] = $0.value }
1226
- paymentDoneVC.additionalInfo = additionalDict
1227
- }
1228
- self.navigationController?.pushViewController(paymentDoneVC, animated: true)
1229
- }
1230
- }
1231
- }
1232
- } else {
1233
- self.presentPaymentErrorVC(errorMessage: "Invalid JSON format")
1234
- }
1235
- } catch let jsonError {
1236
- self.presentPaymentErrorVC(errorMessage: "Error parsing JSON: \(jsonError)")
1237
- }
1238
- } else {
1239
- self.presentPaymentErrorVC(errorMessage: "No data received")
1240
- }
1241
- } else {
1242
- if let data = serviceData,
1243
- let responseObj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
1244
- let message = responseObj["message"] as? String {
1245
- self.presentPaymentErrorVC(errorMessage: message)
1246
- } else {
1247
- self.presentPaymentErrorVC(errorMessage: "HTTP Status Code: \(httpResponse.statusCode)")
1248
- }
1249
- }
1250
- }
1251
- task.resume()
1252
- }
1253
-
1254
- //MARK: - Account Charge Api if billing info is nil and saved account
1255
- func accountChargeWithSavedAccountApi(customerId: String?) {
1256
- showLoadingIndicator()
1257
-
1258
- let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.achCharge.path()
1259
-
1260
- guard let serviceURL = URL(string: fullURL) else {
1261
- print("Invalid URL")
1262
- hideLoadingIndicator()
1263
- return
1264
- }
1265
-
1266
- var uRLRequest = URLRequest(url: serviceURL)
1267
- uRLRequest.httpMethod = "POST"
1268
- uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
1269
-
1270
- let token = UserStoreSingleton.shared.clientToken
1271
- print("Setting clientToken header: \(token ?? "None")")
1272
- uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
1273
-
1274
- let emailPrefix = email?.components(separatedBy: "@").first ?? ""
1275
-
1276
- var params: [String: Any] = [
1277
- // "name": emailPrefix,
1278
- "name": !(request.name?.isEmpty ?? true) ? request.name! : (emailPrefix),
1279
- "email": email ?? "",
1280
- "description": "Test Description",
1281
- "currency": "usd",
1282
- "account_type": accountType?.lowercased() ?? "",
1283
- "routing_number": routingNumber ?? "",
1284
- "account_number": accountNumber ?? "",
1285
- "payment_mode": "auth_and_capture",
1286
- "levelIndicator": 1,
1287
- "save_account": (isSavedNewAccount ?? false) ? 1 : 0,
1288
- "payment_method": "ach"
1289
- ]
1290
-
1291
- if let customerId = customerId {
1292
- params["customer"] = customerId
1293
- } else {
1294
- params["username"] = emailPrefix
1295
- }
1296
-
1297
- if customerId == nil {
1298
- params["create_customer"] = "1"
1299
- }
1300
-
1301
- // Add these if recurring is enabled
1302
- // if let req = request, req.is_recurring == true {
1303
- // if let recurringType = req.recurringStartDateType, recurringType == .custom {
1304
- // // Only send start_date if type is .custom and field is not empty
1305
- // if let startDateText = startDate, !startDateText.isEmpty {
1306
- // let inputFormatter = DateFormatter()
1307
- // inputFormatter.dateFormat = "dd/MM/yyyy"
1308
- //
1309
- // let outputFormatter = DateFormatter()
1310
- // outputFormatter.dateFormat = "MM/dd/yyyy"
1311
- //
1312
- // if let date = inputFormatter.date(from: startDateText) {
1313
- // let apiFormattedDate = outputFormatter.string(from: date)
1314
- // params["start_date"] = apiFormattedDate
1315
- // } else {
1316
- // print("Invalid date format in startDateText")
1317
- // }
1318
- // }
1319
- // }
1320
- //
1321
- // params["interval"] = chosenPlan?.lowercased()
1322
- // }
1323
-
1324
- // Add these if recurring is enabled
1325
- if let req = request, req.is_recurring == true {
1326
- if let startDateText = startDate, !startDateText.isEmpty {
1327
- let inputFormatter = DateFormatter()
1328
- inputFormatter.dateFormat = "dd/MM/yyyy"
1329
-
1330
- let outputFormatter = DateFormatter()
1331
- outputFormatter.dateFormat = "MM/dd/yyyy"
1332
-
1333
- if let date = inputFormatter.date(from: startDateText) {
1334
- let apiFormattedDate = outputFormatter.string(from: date)
1335
- params["start_date"] = apiFormattedDate
1336
- } else {
1337
- print("Invalid date format in startDateText")
1338
- }
1339
- }
1340
-
1341
- // interval is still required
1342
- params["interval"] = chosenPlan?.lowercased()
1343
- }
1344
-
1345
- // ✅ Include metadata only if it has at least 1 key-value pair
1346
- if let metadata = request?.metadata, !metadata.isEmpty {
1347
- params["metadata"] = metadata
1348
- }
1349
-
1350
- print(params)
1351
-
1352
- do {
1353
- let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
1354
- uRLRequest.httpBody = jsonData
1355
- if let jsonString = String(data: jsonData, encoding: .utf8) {
1356
- print("JSON Payload: \(jsonString)")
1357
- }
1358
- } catch let error {
1359
- print("Error creating JSON data: \(error)")
1360
- hideLoadingIndicator()
1361
- return
1362
- }
1363
-
1364
- let session = URLSession.shared
1365
- let task = session.dataTask(with: uRLRequest) { (serviceData, serviceResponse, error) in
1366
-
1367
- DispatchQueue.main.async {
1368
- self.hideLoadingIndicator() // Stop loader when response is received
1369
- }
1370
-
1371
- if let error = error {
1372
- self.presentPaymentErrorVC(errorMessage: error.localizedDescription)
1373
- return
1374
- }
1375
-
1376
- guard let httpResponse = serviceResponse as? HTTPURLResponse else {
1377
- self.presentPaymentErrorVC(errorMessage: "Invalid response")
1378
- return
1379
- }
1380
-
1381
- if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
1382
- if let data = serviceData {
1383
- do {
1384
- if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
1385
- print("Response Data: \(responseObject)")
1386
-
1387
- // ✅ Handle duplicate transaction case
1388
- if let status = responseObject["status"] as? Bool, status == false,
1389
- let message = responseObject["message"] as? String,
1390
- message.lowercased().contains("duplicate transaction") {
1391
- self.presentPaymentErrorVC(errorMessage: message)
1392
- return
1393
- }
1394
-
1395
- if let status = responseObject["status"] as? Int, status == 0,
1396
- let message = responseObject["message"] as? String,
1397
- message.lowercased().contains("duplicate transaction") {
1398
- self.presentPaymentErrorVC(errorMessage: message)
1399
- return
1400
- }
1401
-
1402
- // ✅ Handle generic "status == 0" error case
1403
- if let status = responseObject["status"] as? Int, status == 0 {
1404
- let errorMessage = responseObject["message"] as? String ?? "Unknown error"
1405
- self.presentPaymentErrorVC(errorMessage: errorMessage)
1406
- return
1407
- }
1408
- else {
1409
- DispatchQueue.main.async {
1410
- if let paymentDoneVC = self.storyboard?.instantiateViewController(withIdentifier: "PaymentDoneVC") as? PaymentDoneVC {
1411
- paymentDoneVC.chargeData = responseObject
1412
- paymentDoneVC.selectedPaymentMethod = self.selectedPaymentMethod
1413
- paymentDoneVC.easyPayDelegate = self.easyPayDelegate
1414
- paymentDoneVC.bankPaymentParams = params
1415
- paymentDoneVC.request = self.request
1416
- self.navigationController?.pushViewController(paymentDoneVC, animated: true)
1417
- }
1418
- }
1419
- }
1420
- } else {
1421
- self.presentPaymentErrorVC(errorMessage: "Invalid JSON format")
1422
- }
1423
- } catch let jsonError {
1424
- self.presentPaymentErrorVC(errorMessage: "Error parsing JSON: \(jsonError)")
1425
- }
1426
- } else {
1427
- self.presentPaymentErrorVC(errorMessage: "No data received")
1428
- }
1429
- } else {
1430
- if let data = serviceData,
1431
- let responseObj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
1432
- let message = responseObj["message"] as? String {
1433
- self.presentPaymentErrorVC(errorMessage: message)
1434
- } else {
1435
- self.presentPaymentErrorVC(errorMessage: "HTTP Status Code: \(httpResponse.statusCode)")
1436
- }
1437
- }
1438
- }
1439
- task.resume()
1440
- }
1441
-
1442
- //MARK: - GrailPay Account Charge Api if user saved account
1443
- func grailPayAccountChargeApi(customerId: String?) {
1444
- showLoadingIndicator()
1445
-
1446
- let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.achCharge.path()
1447
-
1448
- guard let serviceURL = URL(string: fullURL) else {
1449
- print("Invalid URL")
1450
- hideLoadingIndicator()
1451
- return
1452
- }
1453
-
1454
- var uRLRequest = URLRequest(url: serviceURL)
1455
- uRLRequest.httpMethod = "POST"
1456
- uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
1457
-
1458
- let token = UserStoreSingleton.shared.clientToken
1459
- print("Setting clientToken header: \(token ?? "None")")
1460
- uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
1461
-
1462
- let emailPrefix = userEmail?.components(separatedBy: "@").first ?? ""
1463
-
1464
- var params: [String: Any] = [
1465
- "account_id": self.grailPayAccountID ?? "",
1466
- "account_type": self.selectedGrailPayAccountType ?? "",
1467
- "name": self.selectedGrailPayAccountName ?? "",
1468
- "save_account": (isSavedNewAccount ?? false) ? 1 : 0,
1469
- "is_default": (isSavedNewAccount ?? false) ? 1 : 0,
1470
- "customer_id": customerId ?? "",
1471
- "email": userEmail ?? "",
1472
- "create_customer": "1",
1473
- ]
1474
-
1475
- if let customerId = customerId {
1476
- params["customer"] = customerId
1477
- } else {
1478
- params["username"] = emailPrefix
1479
- }
1480
-
1481
- if customerId == nil {
1482
- params["create_customer"] = "1"
1483
- }
1484
-
1485
- // // Billing Info
1486
- // if let visibility = visibility, visibility.billing == true,
1487
- // let billing = billingInfo, !billing.isEmpty {
1488
- // var billingDict: [String: Any] = [:]
1489
- // billing.forEach { billingDict[$0.name] = $0.value }
1490
- //
1491
- // params["address"] = billingDict["address"] as? String ?? ""
1492
- // params["country"] = billingDict["country"] as? String ?? ""
1493
- // params["state"] = billingDict["state"] as? String ?? ""
1494
- // params["city"] = billingDict["city"] as? String ?? ""
1495
- // params["zip"] = billingDict["postal_code"] as? String ?? ""
1496
- // }
1497
-
1498
- // Always include Billing Info if available
1499
- if let billing = billingInfo, !billing.isEmpty {
1500
- var billingDict: [String: Any] = [:]
1501
- billing.forEach { billingDict[$0.name] = $0.value }
1502
-
1503
- params["address"] = billingDict["address"] as? String ?? ""
1504
- params["country"] = billingDict["country"] as? String ?? ""
1505
- params["state"] = billingDict["state"] as? String ?? ""
1506
- params["city"] = billingDict["city"] as? String ?? ""
1507
- params["zip"] = billingDict["postal_code"] as? String ?? ""
1508
- }
1509
-
1510
- // // Additional Info or default description
1511
- // var descriptionValue: String = "Hosted payment checkout" // default
1512
- // if let visibility = visibility, visibility.additional == true,
1513
- // let additional = additionalInfo, !additional.isEmpty {
1514
- //
1515
- // var additionalDict: [String: Any] = [:]
1516
- // additional.forEach { additionalDict[$0.name] = $0.value }
1517
- //
1518
- // if let desc = additionalDict["description"] as? String, !desc.isEmpty {
1519
- // descriptionValue = desc
1520
- // }
1521
- //
1522
- // if let phone = additionalDict["phone_number"] as? String, !phone.isEmpty {
1523
- // params["phone_number"] = phone
1524
- // }
1525
- // }
1526
- // params["description"] = descriptionValue
1527
-
1528
- // Always include Additional Info if available
1529
- var descriptionValue: String = "Hosted payment checkout"
1530
- if let additional = additionalInfo, !additional.isEmpty {
1531
- var additionalDict: [String: Any] = [:]
1532
- additional.forEach { additionalDict[$0.name] = $0.value }
1533
-
1534
- if let desc = additionalDict["description"] as? String, !desc.isEmpty {
1535
- descriptionValue = desc
1536
- }
1537
-
1538
- if let phone = additionalDict["phone_number"] as? String, !phone.isEmpty {
1539
- params["phone_number"] = phone
1540
- }
1541
- }
1542
- params["description"] = descriptionValue
1543
-
1544
- // Add these if recurring is enabled
1545
- // if let req = request, req.is_recurring == true {
1546
- // if let recurringType = req.recurringStartDateType, recurringType == .custom {
1547
- // // Only send start_date if type is .custom and field is not empty
1548
- // if let startDateText = startDate, !startDateText.isEmpty {
1549
- // let inputFormatter = DateFormatter()
1550
- // inputFormatter.dateFormat = "dd/MM/yyyy"
1551
- //
1552
- // let outputFormatter = DateFormatter()
1553
- // outputFormatter.dateFormat = "MM/dd/yyyy"
1554
- //
1555
- // if let date = inputFormatter.date(from: startDateText) {
1556
- // let apiFormattedDate = outputFormatter.string(from: date)
1557
- // params["start_date"] = apiFormattedDate
1558
- // } else {
1559
- // print("Invalid date format in startDateText")
1560
- // }
1561
- // }
1562
- // }
1563
- //
1564
- // params["interval"] = chosenPlan?.lowercased()
1565
- // }
1566
-
1567
- // Add these if recurring is enabled
1568
- if let req = request, req.is_recurring == true {
1569
- if let startDateText = startDate, !startDateText.isEmpty {
1570
- let inputFormatter = DateFormatter()
1571
- inputFormatter.dateFormat = "dd/MM/yyyy"
1572
-
1573
- let outputFormatter = DateFormatter()
1574
- outputFormatter.dateFormat = "MM/dd/yyyy"
1575
-
1576
- if let date = inputFormatter.date(from: startDateText) {
1577
- let apiFormattedDate = outputFormatter.string(from: date)
1578
- params["start_date"] = apiFormattedDate
1579
- } else {
1580
- print("Invalid date format in startDateText")
1581
- }
1582
- }
1583
-
1584
- // interval is still required
1585
- params["interval"] = chosenPlan?.lowercased()
1586
- }
1587
-
1588
- // ✅ Include metadata only if it has at least 1 key-value pair
1589
- if let metadata = request?.metadata, !metadata.isEmpty {
1590
- params["metadata"] = metadata
1591
- }
1592
-
1593
- print(params)
1594
-
1595
- do {
1596
- let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
1597
- uRLRequest.httpBody = jsonData
1598
- if let jsonString = String(data: jsonData, encoding: .utf8) {
1599
- print("JSON Payload: \(jsonString)")
1600
- }
1601
- } catch let error {
1602
- print("Error creating JSON data: \(error)")
1603
- hideLoadingIndicator()
1604
- return
1605
- }
1606
-
1607
- let session = URLSession.shared
1608
- let task = session.dataTask(with: uRLRequest) { (serviceData, serviceResponse, error) in
1609
-
1610
- DispatchQueue.main.async {
1611
- self.hideLoadingIndicator() // Stop loader when response is received
1612
- }
1613
-
1614
- if let error = error {
1615
- self.presentPaymentErrorVC(errorMessage: error.localizedDescription)
1616
- return
1617
- }
1618
-
1619
- guard let httpResponse = serviceResponse as? HTTPURLResponse else {
1620
- self.presentPaymentErrorVC(errorMessage: "Invalid response")
1621
- return
1622
- }
1623
-
1624
- if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
1625
- if let data = serviceData {
1626
- do {
1627
- if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
1628
- print("Response Data: \(responseObject)")
1629
-
1630
- // ✅ Handle duplicate transaction case
1631
- if let status = responseObject["status"] as? Bool, status == false,
1632
- let message = responseObject["message"] as? String,
1633
- message.lowercased().contains("duplicate transaction") {
1634
- self.presentPaymentErrorVC(errorMessage: message)
1635
- return
1636
- }
1637
-
1638
- if let status = responseObject["status"] as? Int, status == 0,
1639
- let message = responseObject["message"] as? String,
1640
- message.lowercased().contains("duplicate transaction") {
1641
- self.presentPaymentErrorVC(errorMessage: message)
1642
- return
1643
- }
1644
-
1645
- // ✅ Handle generic "status == 0" error case
1646
- if let status = responseObject["status"] as? Int, status == 0 {
1647
- let errorMessage = responseObject["message"] as? String ?? "Unknown error"
1648
- self.presentPaymentErrorVC(errorMessage: errorMessage)
1649
- return
1650
- }
1651
- else {
1652
- DispatchQueue.main.async {
1653
- if let paymentDoneVC = self.storyboard?.instantiateViewController(withIdentifier: "PaymentDoneVC") as? PaymentDoneVC {
1654
- paymentDoneVC.chargeData = responseObject
1655
- paymentDoneVC.selectedPaymentMethod = self.selectedPaymentMethod
1656
- paymentDoneVC.easyPayDelegate = self.easyPayDelegate
1657
- paymentDoneVC.bankPaymentParams = params
1658
- // Pass billing and additional info
1659
- // Conditionally pass raw FieldItem array
1660
- paymentDoneVC.visibility = self.visibility
1661
- paymentDoneVC.request = self.request
1662
-
1663
- // if self.visibility?.billing == true {
1664
- paymentDoneVC.billingInfoData = self.billingInfo
1665
- var billingDict: [String: Any] = [:]
1666
- self.billingInfo?.forEach { billingDict[$0.name] = $0.value }
1667
- paymentDoneVC.billingInfo = billingDict
1668
- // }
1669
-
1670
- // if self.visibility?.additional == true {
1671
- paymentDoneVC.additionalInfoData = self.additionalInfo
1672
- var additionalDict: [String: Any] = [:]
1673
- self.additionalInfo?.forEach { additionalDict[$0.name] = $0.value }
1674
- paymentDoneVC.additionalInfo = additionalDict
1675
- // }
1676
- self.navigationController?.pushViewController(paymentDoneVC, animated: true)
1677
- }
1678
- }
1679
- }
1680
- } else {
1681
- self.presentPaymentErrorVC(errorMessage: "Invalid JSON format")
1682
- }
1683
- } catch let jsonError {
1684
- self.presentPaymentErrorVC(errorMessage: "Error parsing JSON: \(jsonError)")
1685
- }
1686
- } else {
1687
- self.presentPaymentErrorVC(errorMessage: "No data received")
1688
- }
1689
- } else {
1690
- if let data = serviceData,
1691
- let responseObj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
1692
- let message = responseObj["message"] as? String {
1693
- self.presentPaymentErrorVC(errorMessage: message)
1694
- } else {
1695
- self.presentPaymentErrorVC(errorMessage: "HTTP Status Code: \(httpResponse.statusCode)")
1696
- }
1697
- }
1698
- }
1699
- task.resume()
1700
- }
1701
-
1702
- // MARK: - 3DS Functionality
1703
-
1704
- // MARK: - Credit Card Charge Api If Billing info is nil and Without Login.
1705
- func threeDSecurePaymentApi(customerId: String?) {
1706
- showLoadingIndicator()
1707
-
1708
- let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.threeDSecure.path()
1709
-
1710
- guard let serviceURL = URL(string: fullURL) else {
1711
- print("Invalid URL")
1712
- hideLoadingIndicator()
1713
- return
1714
- }
1715
-
1716
- var uRLRequest = URLRequest(url: serviceURL)
1717
- uRLRequest.httpMethod = "POST"
1718
- uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
1719
-
1720
- let token = UserStoreSingleton.shared.clientToken
1721
- print("Setting clientToken header: \(token ?? "None")")
1722
- uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
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
- "description": "payment checkout",
1733
- "currency": "usd",
1734
- "payment_method": "card",
1735
- "tokenize": request.tokenOnly ?? false,
1736
- "save_card": isSavedNewCard ? 1 : 0,
1737
- "customer_id": customerId ?? ""
1738
- ]
1739
-
1740
- if customerId == nil {
1741
- params["create_customer"] = "1"
1742
- }
1743
-
1744
- // Add these if recurring is enabled
1745
- // if let req = request, req.is_recurring == true {
1746
- // if let recurringType = req.recurringStartDateType, recurringType == .custom {
1747
- // // Only send start_date if type is .custom and field is not empty
1748
- // if let startDateText = startDate, !startDateText.isEmpty {
1749
- // let inputFormatter = DateFormatter()
1750
- // inputFormatter.dateFormat = "dd/MM/yyyy"
1751
- //
1752
- // let outputFormatter = DateFormatter()
1753
- // outputFormatter.dateFormat = "MM/dd/yyyy"
1754
- //
1755
- // if let date = inputFormatter.date(from: startDateText) {
1756
- // let apiFormattedDate = outputFormatter.string(from: date)
1757
- // params["start_date"] = apiFormattedDate
1758
- // } else {
1759
- // print("Invalid date format in startDateText")
1760
- // }
1761
- // }
1762
- // }
1763
- //
1764
- // params["interval"] = chosenPlan?.lowercased()
1765
- // }
1766
-
1767
- // Add these if recurring is enabled
1768
- if let req = request, req.is_recurring == true {
1769
- if let startDateText = startDate, !startDateText.isEmpty {
1770
- let inputFormatter = DateFormatter()
1771
- inputFormatter.dateFormat = "dd/MM/yyyy"
1772
-
1773
- let outputFormatter = DateFormatter()
1774
- outputFormatter.dateFormat = "MM/dd/yyyy"
1775
-
1776
- if let date = inputFormatter.date(from: startDateText) {
1777
- let apiFormattedDate = outputFormatter.string(from: date)
1778
- params["start_date"] = apiFormattedDate
1779
- } else {
1780
- print("Invalid date format in startDateText")
1781
- }
1782
- }
1783
-
1784
- // interval is still required
1785
- params["interval"] = chosenPlan?.lowercased()
1786
- }
1787
-
1788
- // ✅ Include metadata only if it has at least 1 key-value pair
1789
- if let metadata = request?.metadata, !metadata.isEmpty {
1790
- params["metadata"] = metadata
1791
- }
1792
-
1793
- print(params)
1794
-
1795
- do {
1796
- let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
1797
- uRLRequest.httpBody = jsonData
1798
- if let jsonString = String(data: jsonData, encoding: .utf8) {
1799
- print("JSON Payload: \(jsonString)")
1800
- }
1801
- } catch let error {
1802
- print("Error creating JSON data: \(error)")
1803
- hideLoadingIndicator()
1804
- return
1805
- }
1806
-
1807
- let session = URLSession.shared
1808
- let task = session.dataTask(with: uRLRequest) { (serviceData, serviceResponse, error) in
1809
-
1810
- DispatchQueue.main.async {
1811
- self.hideLoadingIndicator() // Stop loader when response is received
1812
- }
1813
-
1814
- if let error = error {
1815
- print("Error: \(error.localizedDescription)")
1816
- return
1817
- }
1818
-
1819
- guard let httpResponse = serviceResponse as? HTTPURLResponse else {
1820
- print("Invalid response")
1821
- return
1822
- }
1823
-
1824
- if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
1825
- if let data = serviceData {
1826
- do {
1827
- if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
1828
- print("Response Data: \(responseObject)")
1829
-
1830
- // ✅ Handle duplicate transaction case
1831
- if let status = responseObject["status"] as? Bool, status == false,
1832
- let message = responseObject["message"] as? String,
1833
- message.lowercased().contains("duplicate transaction") {
1834
- self.presentPaymentErrorVC(errorMessage: message)
1835
- return
1836
- }
1837
-
1838
- if let status = responseObject["status"] as? Int, status == 0,
1839
- let message = responseObject["message"] as? String,
1840
- message.lowercased().contains("duplicate transaction") {
1841
- self.presentPaymentErrorVC(errorMessage: message)
1842
- return
1843
- }
1844
-
1845
- // ✅ Handle generic "status == 0" error case
1846
- if let status = responseObject["status"] as? Int, status == 0 {
1847
- let errorMessage = responseObject["message"] as? String ?? "Unknown error"
1848
- self.presentPaymentErrorVC(errorMessage: errorMessage)
1849
- return
1850
- }
1851
- else {
1852
- DispatchQueue.main.async {
1853
- if let paymentDoneVC = self.storyboard?.instantiateViewController(withIdentifier: "ThreeDSecurePaymentDoneVC") as? ThreeDSecurePaymentDoneVC {
1854
-
1855
- let urlString = responseObject["redirect_url"] as? String ?? responseObject["location_url"] as? String ?? ""
1856
- paymentDoneVC.redirectURL = urlString
1857
- paymentDoneVC.chargeData = responseObject
1858
- paymentDoneVC.easyPayDelegate = self.easyPayDelegate
1859
- paymentDoneVC.cardApiParams = params
1860
- paymentDoneVC.request = self.request
1861
- self.navigationController?.pushViewController(paymentDoneVC, animated: true)
1862
- }
1863
- }
1864
- }
1865
- } else {
1866
- self.presentPaymentErrorVC(errorMessage: "Invalid JSON format")
1867
- }
1868
- } catch let jsonError {
1869
- self.presentPaymentErrorVC(errorMessage: "Error parsing JSON: \(jsonError)")
1870
- }
1871
- } else {
1872
- self.presentPaymentErrorVC(errorMessage: "No data received")
1873
- }
1874
- } else {
1875
- if let data = serviceData,
1876
- let responseObj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
1877
- let message = responseObj["message"] as? String {
1878
- self.presentPaymentErrorVC(errorMessage: message)
1879
- } else {
1880
- self.presentPaymentErrorVC(errorMessage: "HTTP Status Code: \(httpResponse.statusCode)")
1881
- }
1882
- }
1883
- }
1884
- task.resume()
1885
- }
1886
-
1887
- // MARK: - Credit Card Charge Api If Billing info is not nil and Without Login.
1888
- func threeDSecurePaymentSavedCardApi(customerId: String?) {
1889
- showLoadingIndicator()
1890
-
1891
- let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.threeDSecure.path()
1892
-
1893
- guard let serviceURL = URL(string: fullURL) else {
1894
- print("Invalid URL")
1895
- hideLoadingIndicator()
1896
- return
1897
- }
1898
-
1899
- var uRLRequest = URLRequest(url: serviceURL)
1900
- uRLRequest.httpMethod = "POST"
1901
- uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
1902
-
1903
- let token = UserStoreSingleton.shared.clientToken
1904
- print("Setting clientToken header: \(token ?? "None")")
1905
- uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
1906
-
1907
- var params: [String: Any] = [
1908
- "name": nameOnCard ?? "",
1909
- "email": email ?? "",
1910
- "card_number": cardNumber?.replacingOccurrences(of: " ", with: "") ?? "",
1911
- "cardholder_name": nameOnCard ?? "",
1912
- "exp_month": expiryDate?.components(separatedBy: "/").first ?? "",
1913
- "exp_year": expiryDate?.components(separatedBy: "/").last ?? "",
1914
- "cvc": cvv ?? "",
1915
- "currency": "usd",
1916
- "tokenize": request.tokenOnly ?? false,
1917
- "save_card": isSavedNewCard ? 1 : 0,
1918
- "customer_id": customerId ?? "",
1919
- "customer" : customerId ?? ""
1920
- ]
1921
-
1922
- // Add is_default parameter if save_card is 1
1923
- if isSavedNewCard {
1924
- params["is_default"] = "1"
1925
- }
1926
-
1927
- if customerId == nil {
1928
- params["create_customer"] = "1"
1929
- }
1930
-
1931
- // Always include Billing Info if available
1932
- if let billing = billingInfo, !billing.isEmpty {
1933
- var billingDict: [String: Any] = [:]
1934
- billing.forEach { billingDict[$0.name] = $0.value }
1935
-
1936
- params["address"] = billingDict["address"] as? String ?? ""
1937
- params["country"] = billingDict["country"] as? String ?? ""
1938
- params["state"] = billingDict["state"] as? String ?? ""
1939
- params["city"] = billingDict["city"] as? String ?? ""
1940
- params["zip"] = billingDict["postal_code"] as? String ?? ""
1941
- }
1942
-
1943
- // Always include Additional Info if available
1944
- var descriptionValue: String = "Hosted payment checkout"
1945
- if let additional = additionalInfo, !additional.isEmpty {
1946
- var additionalDict: [String: Any] = [:]
1947
- additional.forEach { additionalDict[$0.name] = $0.value }
1948
-
1949
- if let desc = additionalDict["description"] as? String, !desc.isEmpty {
1950
- descriptionValue = desc
1951
- }
1952
-
1953
- if let phone = additionalDict["phone_number"] as? String, !phone.isEmpty {
1954
- params["phone_number"] = phone
1955
- }
1956
- }
1957
- params["description"] = descriptionValue
1958
-
1959
- // Add these if recurring is enabled
1960
- // if let req = request, req.is_recurring == true {
1961
- // if let recurringType = req.recurringStartDateType, recurringType == .custom {
1962
- // // Only send start_date if type is .custom and field is not empty
1963
- // if let startDateText = startDate, !startDateText.isEmpty {
1964
- // let inputFormatter = DateFormatter()
1965
- // inputFormatter.dateFormat = "dd/MM/yyyy"
1966
- //
1967
- // let outputFormatter = DateFormatter()
1968
- // outputFormatter.dateFormat = "MM/dd/yyyy"
1969
- //
1970
- // if let date = inputFormatter.date(from: startDateText) {
1971
- // let apiFormattedDate = outputFormatter.string(from: date)
1972
- // params["start_date"] = apiFormattedDate
1973
- // } else {
1974
- // print("Invalid date format in startDateText")
1975
- // }
1976
- // }
1977
- // }
1978
- //
1979
- // params["interval"] = chosenPlan?.lowercased()
1980
- // }
1981
-
1982
- // Add these if recurring is enabled
1983
- if let req = request, req.is_recurring == true {
1984
- if let startDateText = startDate, !startDateText.isEmpty {
1985
- let inputFormatter = DateFormatter()
1986
- inputFormatter.dateFormat = "dd/MM/yyyy"
1987
-
1988
- let outputFormatter = DateFormatter()
1989
- outputFormatter.dateFormat = "MM/dd/yyyy"
1990
-
1991
- if let date = inputFormatter.date(from: startDateText) {
1992
- let apiFormattedDate = outputFormatter.string(from: date)
1993
- params["start_date"] = apiFormattedDate
1994
- } else {
1995
- print("Invalid date format in startDateText")
1996
- }
1997
- }
1998
-
1999
- // interval is still required
2000
- params["interval"] = chosenPlan?.lowercased()
2001
- }
2002
-
2003
- // ✅ Include metadata only if it has at least 1 key-value pair
2004
- if let metadata = request?.metadata, !metadata.isEmpty {
2005
- params["metadata"] = metadata
2006
- }
2007
-
2008
- print(params)
2009
-
2010
- do {
2011
- let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
2012
- uRLRequest.httpBody = jsonData
2013
- if let jsonString = String(data: jsonData, encoding: .utf8) {
2014
- print("JSON Payload: \(jsonString)")
2015
- }
2016
- } catch let error {
2017
- print("Error creating JSON data: \(error)")
2018
- hideLoadingIndicator()
2019
- return
2020
- }
2021
-
2022
- let session = URLSession.shared
2023
- let task = session.dataTask(with: uRLRequest) { (serviceData, serviceResponse, error) in
2024
-
2025
- DispatchQueue.main.async {
2026
- self.hideLoadingIndicator() // Stop loader when response is received
2027
- }
2028
-
2029
- if let error = error {
2030
- print("Error: \(error.localizedDescription)")
2031
- return
2032
- }
2033
-
2034
- guard let httpResponse = serviceResponse as? HTTPURLResponse else {
2035
- print("Invalid response")
2036
- return
2037
- }
2038
-
2039
- if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
2040
- if let data = serviceData {
2041
- do {
2042
- if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
2043
- print("Response Data: \(responseObject)")
2044
-
2045
- // ✅ Handle duplicate transaction case
2046
- if let status = responseObject["status"] as? Bool, status == false,
2047
- let message = responseObject["message"] as? String,
2048
- message.lowercased().contains("duplicate transaction") {
2049
- self.presentPaymentErrorVC(errorMessage: message)
2050
- return
2051
- }
2052
-
2053
- if let status = responseObject["status"] as? Int, status == 0,
2054
- let message = responseObject["message"] as? String,
2055
- message.lowercased().contains("duplicate transaction") {
2056
- self.presentPaymentErrorVC(errorMessage: message)
2057
- return
2058
- }
2059
-
2060
- // ✅ Handle generic "status == 0" error case
2061
- if let status = responseObject["status"] as? Int, status == 0 {
2062
- let errorMessage = responseObject["message"] as? String ?? "Unknown error"
2063
- self.presentPaymentErrorVC(errorMessage: errorMessage)
2064
- return
2065
- }
2066
- else {
2067
- DispatchQueue.main.async {
2068
- if let paymentDoneVC = self.storyboard?.instantiateViewController(withIdentifier: "ThreeDSecurePaymentDoneVC") as? ThreeDSecurePaymentDoneVC {
2069
-
2070
- let urlString = responseObject["redirect_url"] as? String ?? responseObject["location_url"] as? String ?? ""
2071
- paymentDoneVC.redirectURL = urlString
2072
- paymentDoneVC.chargeData = responseObject
2073
- paymentDoneVC.easyPayDelegate = self.easyPayDelegate
2074
- // Pass billing and additional info
2075
- // Conditionally pass raw FieldItem array
2076
- paymentDoneVC.visibility = self.visibility
2077
- paymentDoneVC.amount = self.amount
2078
- paymentDoneVC.cardApiParams = params
2079
- paymentDoneVC.request = self.request
2080
-
2081
- // if self.visibility?.billing == true {
2082
- paymentDoneVC.billingInfoData = self.billingInfo
2083
- var billingDict: [String: Any] = [:]
2084
- self.billingInfo?.forEach { billingDict[$0.name] = $0.value }
2085
- paymentDoneVC.billingInfo = billingDict
2086
- // }
2087
-
2088
- // if self.visibility?.additional == true {
2089
- paymentDoneVC.additionalInfoData = self.additionalInfo
2090
- var additionalDict: [String: Any] = [:]
2091
- self.additionalInfo?.forEach { additionalDict[$0.name] = $0.value }
2092
- paymentDoneVC.additionalInfo = additionalDict
2093
- // }
2094
- self.navigationController?.pushViewController(paymentDoneVC, animated: true)
2095
- }
2096
- }
2097
- }
2098
- } else {
2099
- self.presentPaymentErrorVC(errorMessage: "Invalid JSON format")
2100
- }
2101
- } catch let jsonError {
2102
- self.presentPaymentErrorVC(errorMessage: "Error parsing JSON: \(jsonError)")
2103
- }
2104
- } else {
2105
- self.presentPaymentErrorVC(errorMessage: "No data received")
2106
- }
2107
- } else {
2108
- if let data = serviceData,
2109
- let responseObj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
2110
- let message = responseObj["message"] as? String {
2111
- self.presentPaymentErrorVC(errorMessage: message)
2112
- } else {
2113
- self.presentPaymentErrorVC(errorMessage: "HTTP Status Code: \(httpResponse.statusCode)")
2114
- }
2115
- }
2116
- }
2117
- task.resume()
2118
- }
2119
-
2120
- @IBAction func actionBtnCancel(_ sender: UIButton) {
2121
- stopTimer()
2122
- for viewController in self.navigationController?.viewControllers ?? [] {
2123
- if viewController is PaymentInfoVC {
2124
- self.navigationController?.popToViewController(viewController, animated: true)
2125
- break
2126
- }
2127
- }
2128
- }
2129
-
2130
- @IBAction func actionBtnResendOTP(_ sender: UIButton) {
2131
- startTimer()
2132
- btnResendOTP.isHidden = true
2133
- imgEsclamationMark.isHidden = false
2134
- lblOtpTimer.isHidden = false
2135
- lblUntillResendOtp.isHidden = false
2136
-
2137
- // Call email verification APi
2138
- emailVerificationApi()
2139
- }
2140
-
2141
- @IBAction func actionBtnConfirmCode(_ sender: UIButton) {
2142
- otpVerificationApi()
2143
- }
2144
-
2145
- }
2146
-
2147
- extension OTPVerificationVC: UITextFieldDelegate {
2148
-
2149
- func textFieldDidChangeSelection(_ textField: UITextField) {
2150
- guard let otpCode = textField.text, otpCode.count >= 6, textField.textContentType == .oneTimeCode else { return }
2151
- txtFieldOTPText1.text = String(otpCode[otpCode.index(otpCode.startIndex, offsetBy: 0)])
2152
- txtFieldOTPText2.text = String(otpCode[otpCode.index(otpCode.startIndex, offsetBy: 1)])
2153
- txtFieldOTPText3.text = String(otpCode[otpCode.index(otpCode.startIndex, offsetBy: 2)])
2154
- txtFieldOTPText4.text = String(otpCode[otpCode.index(otpCode.startIndex, offsetBy: 3)])
2155
- txtFieldOTPText5.text = String(otpCode[otpCode.index(otpCode.startIndex, offsetBy: 4)])
2156
- txtFieldOTPText6.text = String(otpCode[otpCode.index(otpCode.startIndex, offsetBy: 5)])
2157
- txtFieldOTPText6.becomeFirstResponder()
2158
- }
2159
-
2160
- func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
2161
- if [txtFieldOTPText1, txtFieldOTPText2, txtFieldOTPText3, txtFieldOTPText4, txtFieldOTPText5, txtFieldOTPText6].contains(textField) {
2162
- if string.count == 1 {
2163
- // Single character input
2164
- textField.text = string
2165
- switch textField {
2166
- case txtFieldOTPText1:
2167
- txtFieldOTPText2.becomeFirstResponder()
2168
- case txtFieldOTPText2:
2169
- txtFieldOTPText3.becomeFirstResponder()
2170
- case txtFieldOTPText3:
2171
- txtFieldOTPText4.becomeFirstResponder()
2172
- case txtFieldOTPText4:
2173
- txtFieldOTPText5.becomeFirstResponder()
2174
- case txtFieldOTPText5:
2175
- txtFieldOTPText6.becomeFirstResponder()
2176
- case txtFieldOTPText6:
2177
- txtFieldOTPText6.resignFirstResponder()
2178
- // Call OTP verification API when the last field is filled
2179
- if getCombinedOTP().count == 6 {
2180
- // otpVerificationApi()
2181
- }
2182
- default:
2183
- break
2184
- }
2185
- updateViewColors()
2186
- return false
2187
- } else if string.isEmpty {
2188
- // Handle backspace
2189
- textField.text = ""
2190
- switch textField {
2191
- case txtFieldOTPText6:
2192
- txtFieldOTPText5.becomeFirstResponder()
2193
- case txtFieldOTPText5:
2194
- txtFieldOTPText4.becomeFirstResponder()
2195
- case txtFieldOTPText4:
2196
- txtFieldOTPText3.becomeFirstResponder()
2197
- case txtFieldOTPText3:
2198
- txtFieldOTPText2.becomeFirstResponder()
2199
- case txtFieldOTPText2:
2200
- txtFieldOTPText1.becomeFirstResponder()
2201
- default:
2202
- break
2203
- }
2204
- updateViewColors()
2205
- return false
2206
- }
2207
- return textField.text?.count == 0
2208
- }
2209
-
2210
- return true
2211
- }
2212
-
2213
- // Update the view colors based on text presence in OTP fields
2214
- private func updateViewColors() {
2215
- if let primaryBtnBackgroundColor = UserStoreSingleton.shared.primary_btn_bg_col,
2216
- let primaryUIColor = UIColor(hex: primaryBtnBackgroundColor),
2217
- let secondaryFontColor = UserStoreSingleton.shared.secondary_font_col,
2218
- let secondaryUIColor = UIColor(hex: secondaryFontColor) {
2219
-
2220
- viewTextOTP1.layer.borderWidth = 1.0
2221
- viewTextOTP1.layer.borderColor = txtFieldOTPText1.text?.isEmpty == false ? primaryUIColor.cgColor : secondaryUIColor.cgColor
2222
- viewTextOTP2.layer.borderWidth = 1.0
2223
- viewTextOTP2.layer.borderColor = txtFieldOTPText2.text?.isEmpty == false ? primaryUIColor.cgColor : secondaryUIColor.cgColor
2224
- viewTextOTP3.layer.borderWidth = 1.0
2225
- viewTextOTP3.layer.borderColor = txtFieldOTPText3.text?.isEmpty == false ? primaryUIColor.cgColor : secondaryUIColor.cgColor
2226
- viewTextOTP4.layer.borderWidth = 1.0
2227
- viewTextOTP4.layer.borderColor = txtFieldOTPText4.text?.isEmpty == false ? primaryUIColor.cgColor : secondaryUIColor.cgColor
2228
- viewTextOTP5.layer.borderWidth = 1.0
2229
- viewTextOTP5.layer.borderColor = txtFieldOTPText5.text?.isEmpty == false ? primaryUIColor.cgColor : secondaryUIColor.cgColor
2230
- viewTextOTP6.layer.borderWidth = 1.0
2231
- viewTextOTP6.layer.borderColor = txtFieldOTPText6.text?.isEmpty == false ? primaryUIColor.cgColor : secondaryUIColor.cgColor
2232
- }
2233
- else {
2234
- viewTextOTP1.layer.borderWidth = 1.0
2235
- viewTextOTP1.layer.borderColor = (txtFieldOTPText1.text?.isEmpty == false ? UIColor.systemBlue : UIColor.systemGray).cgColor
2236
- viewTextOTP2.layer.borderWidth = 1.0
2237
- viewTextOTP2.layer.borderColor = (txtFieldOTPText2.text?.isEmpty == false ? UIColor.systemBlue : UIColor.systemGray).cgColor
2238
- viewTextOTP3.layer.borderWidth = 1.0
2239
- viewTextOTP3.layer.borderColor = (txtFieldOTPText3.text?.isEmpty == false ? UIColor.systemBlue : UIColor.systemGray).cgColor
2240
- viewTextOTP4.layer.borderWidth = 1.0
2241
- viewTextOTP4.layer.borderColor = (txtFieldOTPText4.text?.isEmpty == false ? UIColor.systemBlue : UIColor.systemGray).cgColor
2242
- viewTextOTP5.layer.borderWidth = 1.0
2243
- viewTextOTP5.layer.borderColor = (txtFieldOTPText5.text?.isEmpty == false ? UIColor.systemBlue : UIColor.systemGray).cgColor
2244
- viewTextOTP6.layer.borderWidth = 1.0
2245
- viewTextOTP6.layer.borderColor = (txtFieldOTPText6.text?.isEmpty == false ? UIColor.systemBlue : UIColor.systemGray).cgColor
2246
- }
2247
- }
2248
-
2249
- @objc func textFieldDidChange(_ textField: UITextField) {
2250
- guard let otpCode = textField.text, otpCode.count >= 6, textField.textContentType == .oneTimeCode else { return }
2251
- // Split the OTP into each text field
2252
- txtFieldOTPText1.text = String(otpCode[otpCode.index(otpCode.startIndex, offsetBy: 0)])
2253
- txtFieldOTPText2.text = String(otpCode[otpCode.index(otpCode.startIndex, offsetBy: 1)])
2254
- txtFieldOTPText3.text = String(otpCode[otpCode.index(otpCode.startIndex, offsetBy: 2)])
2255
- txtFieldOTPText4.text = String(otpCode[otpCode.index(otpCode.startIndex, offsetBy: 3)])
2256
- txtFieldOTPText5.text = String(otpCode[otpCode.index(otpCode.startIndex, offsetBy: 4)])
2257
- txtFieldOTPText6.text = String(otpCode[otpCode.index(otpCode.startIndex, offsetBy: 5)])
2258
- // Automatically move to the last text field
2259
- txtFieldOTPText6.becomeFirstResponder()
2260
- }
2261
-
2262
- func getCombinedOTP() -> String {
2263
- let otp1 = txtFieldOTPText1.text ?? ""
2264
- let otp2 = txtFieldOTPText2.text ?? ""
2265
- let otp3 = txtFieldOTPText3.text ?? ""
2266
- let otp4 = txtFieldOTPText4.text ?? ""
2267
- let otp5 = txtFieldOTPText5.text ?? ""
2268
- let otp6 = txtFieldOTPText6.text ?? ""
2269
-
2270
- return otp1 + otp2 + otp3 + otp4 + otp5 + otp6
2271
- }
2272
-
2273
- }
2274
-
2275
-
2276
-
2277
-
2278
-