@jimrising/easymerchantsdk-react-native 2.2.1 → 2.2.3

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 (28) hide show
  1. package/README.md +1 -1
  2. package/ios/Pods/UserDefaults/UserStoreSingleton.swift +304 -0
  3. package/ios/Pods/ViewControllers/AdditionalInfoVC.swift +2636 -0
  4. package/ios/Pods/ViewControllers/BaseVC.swift +141 -0
  5. package/ios/Pods/ViewControllers/BillingInfoVC/BillingInfoVC.swift +3347 -0
  6. package/ios/Pods/ViewControllers/BillingInfoVC/Cells/CityListTVC.swift +46 -0
  7. package/ios/Pods/ViewControllers/BillingInfoVC/Cells/CountryListTVC.swift +47 -0
  8. package/ios/Pods/ViewControllers/BillingInfoVC/Cells/StateListTVC.swift +46 -0
  9. package/ios/Pods/ViewControllers/CountryListVC.swift +435 -0
  10. package/ios/Pods/ViewControllers/CustomOverlay.swift +199 -0
  11. package/ios/Pods/ViewControllers/EmailVerificationVC.swift +307 -0
  12. package/ios/Pods/ViewControllers/GrailPayVC.swift +244 -0
  13. package/ios/Pods/ViewControllers/OTPVerificationVC.swift +2066 -0
  14. package/ios/Pods/ViewControllers/PaymentDoneVC.swift +272 -0
  15. package/ios/Pods/ViewControllers/PaymentErrorVC.swift +85 -0
  16. package/ios/Pods/ViewControllers/PaymentInformation/AccountTypeTVC.swift +41 -0
  17. package/ios/Pods/ViewControllers/PaymentInformation/PaymentInfoVC.swift +11025 -0
  18. package/ios/Pods/ViewControllers/PaymentInformation/PaymentInformationCVC.swift +35 -0
  19. package/ios/Pods/ViewControllers/PaymentInformation/RecurringTVC.swift +40 -0
  20. package/ios/Pods/ViewControllers/PaymentInformation/SavedAccountsTVC/SavedAccountTVC.swift +81 -0
  21. package/ios/Pods/ViewControllers/PaymentInformation/SavedAccountsTVC/SavedAccountTVC.xib +163 -0
  22. package/ios/Pods/ViewControllers/PaymentInformation/SavedCardsTVC/SavedCardsTVC.swift +81 -0
  23. package/ios/Pods/ViewControllers/PaymentInformation/SavedCardsTVC/SavedCardsTVC.xib +188 -0
  24. package/ios/Pods/ViewControllers/PaymentStatusWebViewVC.swift +167 -0
  25. package/ios/Pods/ViewControllers/TermAndConditionsVC.swift +63 -0
  26. package/ios/Pods/ViewControllers/ThreeDSecurePaymentDoneVC.swift +629 -0
  27. package/ios/easymerchantsdk.podspec +1 -1
  28. package/package.json +1 -1
@@ -0,0 +1,2066 @@
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
+ // Add API headers
301
+ if let apiKey = EnvironmentConfig.apiKey,
302
+ let apiSecret = EnvironmentConfig.apiSecret {
303
+ request.addValue(apiKey, forHTTPHeaderField: "x-api-key")
304
+ request.addValue(apiSecret, forHTTPHeaderField: "x-api-secret")
305
+ }
306
+
307
+ let params: [String: Any] = [
308
+ "card_search_value": email ?? "",
309
+ "card_search_key": "email"
310
+ ]
311
+
312
+ do {
313
+ let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
314
+ request.httpBody = jsonData
315
+ if let jsonString = String(data: jsonData, encoding: .utf8) {
316
+ print("JSON Payload: \(jsonString)")
317
+ }
318
+ } catch let error {
319
+ print("Error creating JSON data: \(error)")
320
+ hideLoadingIndicator()
321
+ return
322
+ }
323
+
324
+ let session = URLSession.shared
325
+ let task = session.dataTask(with: request) { (serviceData, serviceResponse, error) in
326
+
327
+ DispatchQueue.main.async {
328
+ self.hideLoadingIndicator() // Stop loader when response is received
329
+ }
330
+
331
+ if let error = error {
332
+ print("Error: \(error.localizedDescription)")
333
+ return
334
+ }
335
+
336
+ guard let httpResponse = serviceResponse as? HTTPURLResponse else {
337
+ print("Invalid response")
338
+ return
339
+ }
340
+
341
+ if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
342
+ if let data = serviceData {
343
+ do {
344
+ if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
345
+ print("Response Data: \(responseObject)")
346
+
347
+ } else {
348
+ print("Invalid JSON format")
349
+ }
350
+ } catch let jsonError {
351
+ print("Error parsing JSON: \(jsonError)")
352
+ }
353
+ } else {
354
+ print("No data received")
355
+ }
356
+ } else {
357
+ print("HTTP Status Code: \(httpResponse.statusCode)")
358
+ }
359
+ }
360
+ task.resume()
361
+ }
362
+
363
+ //MARK: - OTP Verification Api
364
+ func otpVerificationApi() {
365
+ showLoadingIndicator()
366
+
367
+ let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.verifyOtp.path()
368
+
369
+ guard let serviceURL = URL(string: fullURL) else {
370
+ print("Invalid URL")
371
+ hideLoadingIndicator()
372
+ return
373
+ }
374
+
375
+ var uRLRequest = URLRequest(url: serviceURL)
376
+ uRLRequest.httpMethod = "POST"
377
+ uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
378
+
379
+ let token = UserStoreSingleton.shared.clientToken
380
+ print("Setting clientToken header: \(token ?? "None")")
381
+ uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
382
+
383
+ // Add API headers
384
+ if let apiKey = EnvironmentConfig.apiKey,
385
+ let apiSecret = EnvironmentConfig.apiSecret {
386
+ uRLRequest.addValue(apiKey, forHTTPHeaderField: "x-api-key")
387
+ uRLRequest.addValue(apiSecret, forHTTPHeaderField: "x-api-secret")
388
+ }
389
+
390
+ let params: [String: Any] = [
391
+ "card_search_value": email ?? "",
392
+ "card_search_key": "email",
393
+ "otp": getCombinedOTP()
394
+ ]
395
+
396
+ do {
397
+ let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
398
+ uRLRequest.httpBody = jsonData
399
+ if let jsonString = String(data: jsonData, encoding: .utf8) {
400
+ print("JSON Payload: \(jsonString)")
401
+ }
402
+ } catch let error {
403
+ print("Error creating JSON data: \(error)")
404
+ hideLoadingIndicator()
405
+ return
406
+ }
407
+
408
+ let session = URLSession.shared
409
+ let task = session.dataTask(with: uRLRequest) { (serviceData, serviceResponse, error) in
410
+
411
+ DispatchQueue.main.async {
412
+ self.hideLoadingIndicator() // Stop loader when response is received
413
+ }
414
+
415
+ if let error = error {
416
+ print("Error: \(error.localizedDescription)")
417
+ return
418
+ }
419
+
420
+ guard let httpResponse = serviceResponse as? HTTPURLResponse else {
421
+ print("Invalid response")
422
+ return
423
+ }
424
+
425
+ if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
426
+ if let data = serviceData {
427
+ do {
428
+ if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
429
+ print("Response Data: \(responseObject)")
430
+ if self.selectedPaymentMethod == "Card"{
431
+ if let dataSection = responseObject["data"] as? [String: Any],
432
+ let innerData = dataSection["data"] as? [String: Any],
433
+ let customerId = innerData["customer_id"] as? String {
434
+ print("Extracted customer_id: \(customerId)")
435
+ if self.billingInfoData == nil {
436
+ if self.request.secureAuthentication == true {
437
+ self.threeDSecurePaymentApi(customerId: customerId)
438
+ }
439
+ else {
440
+ self.paymentIntentWithSavedCardApi(customerId: customerId)
441
+ }
442
+ }
443
+ else {
444
+ // self.paymentIntentApi(customerId: customerId)
445
+ if let request = self.request {
446
+ if request.secureAuthentication == true {
447
+ self.threeDSecurePaymentSavedCardApi(customerId: customerId)
448
+ } else {
449
+ self.paymentIntentApi(customerId: customerId)
450
+ }
451
+ }
452
+ }
453
+ } else {
454
+ print("customer_id not found in nested data. Falling back to nil.")
455
+ if let request = self.request {
456
+ if request.secureAuthentication == true {
457
+ self.threeDSecurePaymentSavedCardApi(customerId: nil)
458
+ }
459
+ else {
460
+ self.paymentIntentApi(customerId: nil)
461
+ }
462
+ }
463
+ }
464
+ }
465
+ else if self.selectedPaymentMethod == "Bank" {
466
+
467
+ if let dataSection = responseObject["data"] as? [String: Any],
468
+ let innerData = dataSection["data"] as? [String: Any],
469
+ let customerId = innerData["customer_id"] as? String {
470
+ print("Extracted customer_id: \(customerId)")
471
+ if self.billingInfoData == nil {
472
+ self.accountChargeWithSavedAccountApi(customerId: customerId)
473
+ }
474
+ else {
475
+ self.accountChargeApi(customerId: customerId)
476
+ }
477
+ } else {
478
+ print("customer_id not found in nested data. Falling back to nil.")
479
+ if self.billingInfoData == nil {
480
+ self.accountChargeWithSavedAccountApi(customerId: nil)
481
+ }
482
+ else {
483
+ self.accountChargeApi(customerId: nil)
484
+ }
485
+ }
486
+ }
487
+ else if self.selectedPaymentMethod == "GrailPay" {
488
+ if let dataSection = responseObject["data"] as? [String: Any],
489
+ let innerData = dataSection["data"] as? [String: Any],
490
+ let customerId = innerData["customer_id"] as? String {
491
+ print("Extracted customer_id: \(customerId)")
492
+ self.grailPayAccountChargeApi(customerId: customerId)
493
+ } else {
494
+ print("customer_id not found. Sending empty customerId.")
495
+ self.grailPayAccountChargeApi(customerId: nil)
496
+ }
497
+ }
498
+ } else {
499
+ print("Invalid JSON format")
500
+ }
501
+ } catch let jsonError {
502
+ print("Error parsing JSON: \(jsonError)")
503
+ }
504
+ } else {
505
+ print("No data received")
506
+ }
507
+ } else {
508
+ print("HTTP Status Code: \(httpResponse.statusCode)")
509
+ if let data = serviceData,
510
+ let responseObj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
511
+ let message = responseObj["message"] as? String {
512
+ DispatchQueue.main.async {
513
+ self.showToast(message: message)
514
+ }
515
+ }
516
+ }
517
+ }
518
+ task.resume()
519
+ }
520
+
521
+ //MARK: - Card Charge Api
522
+ func paymentIntentApi(customerId: String?) {
523
+ showLoadingIndicator()
524
+
525
+ let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.charges.path()
526
+
527
+ guard let serviceURL = URL(string: fullURL) else {
528
+ print("Invalid URL")
529
+ hideLoadingIndicator()
530
+ return
531
+ }
532
+
533
+ var uRLRequest = URLRequest(url: serviceURL)
534
+ uRLRequest.httpMethod = "POST"
535
+ uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
536
+
537
+ let token = UserStoreSingleton.shared.clientToken
538
+ print("Setting clientToken header: \(token ?? "None")")
539
+ uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
540
+
541
+ // Add API headers
542
+ if let apiKey = EnvironmentConfig.apiKey,
543
+ let apiSecret = EnvironmentConfig.apiSecret {
544
+ uRLRequest.addValue(apiKey, forHTTPHeaderField: "x-api-key")
545
+ uRLRequest.addValue(apiSecret, forHTTPHeaderField: "x-api-secret")
546
+ }
547
+
548
+ let emailPrefix = userEmail?.components(separatedBy: "@").first ?? ""
549
+
550
+ var params: [String: Any] = [
551
+ "name": nameOnCard ?? "",
552
+ "card_number": cardNumber?.replacingOccurrences(of: " ", with: "") ?? "",
553
+ "cardholder_name": nameOnCard ?? "",
554
+ "exp_month": expiryDate?.components(separatedBy: "/").first ?? "",
555
+ "exp_year": expiryDate?.components(separatedBy: "/").last ?? "",
556
+ "cvc": cvv ?? "",
557
+ "currency": "usd",
558
+ "payment_method": selectedPaymentMethod ?? "",
559
+ "save_card": 1,
560
+ "is_default": "1",
561
+ // "create_customer": "1"
562
+ "email": userEmail ?? "",
563
+ // "price":"6.0",
564
+ "tokenize": request.tokenOnly ?? "",
565
+ "username": emailPrefix
566
+ ]
567
+
568
+ if let customerId = customerId {
569
+ params["customer"] = customerId
570
+ params["customer_id"] = customerId
571
+ }
572
+ // else {
573
+ // params["username"] = emailPrefix
574
+ // params["email"] = userEmail ?? ""
575
+ // }
576
+
577
+ if customerId == nil {
578
+ params["create_customer"] = "1"
579
+ }
580
+
581
+ if let billingInfoData = request.fields {
582
+ do {
583
+ let fieldSection = try JSONDecoder().decode(FieldSection.self, from: billingInfoData)
584
+
585
+ // Billing Info
586
+ let billing = fieldSection.billing
587
+ if !billing.isEmpty {
588
+ var billingDict: [String: Any] = [:]
589
+ billing.forEach { billingDict[$0.name] = $0.value }
590
+
591
+ if let address = billingDict["address"] as? String, !address.isEmpty {
592
+ params["address"] = address
593
+ }
594
+ if let country = billingDict["country"] as? String, !country.isEmpty {
595
+ params["country"] = country
596
+ }
597
+ if let state = billingDict["state"] as? String, !state.isEmpty {
598
+ params["state"] = state
599
+ }
600
+ if let city = billingDict["city"] as? String, !city.isEmpty {
601
+ params["city"] = city
602
+ }
603
+ if let postalCode = billingDict["postal_code"] as? String, !postalCode.isEmpty {
604
+ params["zip"] = postalCode
605
+ }
606
+ }
607
+
608
+ // Additional Info
609
+ let additional = fieldSection.additional
610
+ if !additional.isEmpty {
611
+ var additionalDict: [String: Any] = [:]
612
+ additional.forEach { additionalDict[$0.name] = $0.value }
613
+
614
+ if let desc = additionalDict["description"] as? String, !desc.isEmpty {
615
+ params["description"] = desc
616
+ } else {
617
+ params["description"] = "Hosted payment checkout"
618
+ }
619
+
620
+ if let phone = additionalDict["phone_number"] as? String, !phone.isEmpty {
621
+ params["phone_number"] = phone
622
+ }
623
+ if let email = additionalDict["email"] as? String, !email.isEmpty {
624
+ params["email"] = email
625
+ }
626
+ if let name = additionalDict["name"] as? String, !name.isEmpty {
627
+ params["name"] = name
628
+ }
629
+ } else {
630
+ // If no description in additional info, set default
631
+ params["description"] = "Hosted payment checkout"
632
+ }
633
+
634
+ } catch {
635
+ print("Failed to decode FieldSection: \(error)")
636
+ params["description"] = "Hosted payment checkout"
637
+ }
638
+ } else {
639
+ // Fallback if billingInfoData is missing
640
+ params["description"] = "Hosted payment checkout"
641
+ }
642
+
643
+ // Add these if recurring is enabled
644
+ if let req = request, req.is_recurring == true {
645
+ if let recurringType = req.recurringStartDateType, recurringType == .custom {
646
+ // Only send start_date if type is .custom and field is not empty
647
+ if let startDateText = startDate, !startDateText.isEmpty {
648
+ let inputFormatter = DateFormatter()
649
+ inputFormatter.dateFormat = "dd/MM/yyyy"
650
+
651
+ let outputFormatter = DateFormatter()
652
+ outputFormatter.dateFormat = "MM/dd/yyyy"
653
+
654
+ if let date = inputFormatter.date(from: startDateText) {
655
+ let apiFormattedDate = outputFormatter.string(from: date)
656
+ params["start_date"] = apiFormattedDate
657
+ } else {
658
+ print("Invalid date format in startDateText")
659
+ }
660
+ }
661
+ }
662
+
663
+ params["interval"] = chosenPlan?.lowercased()
664
+ }
665
+
666
+ // ✅ Include metadata only if it has at least 1 key-value pair
667
+ if let metadata = request?.metadata, !metadata.isEmpty {
668
+ params["metadata"] = metadata
669
+ }
670
+
671
+ print(params)
672
+
673
+ do {
674
+ let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
675
+ uRLRequest.httpBody = jsonData
676
+ if let jsonString = String(data: jsonData, encoding: .utf8) {
677
+ print("JSON Payload: \(jsonString)")
678
+ }
679
+ } catch let error {
680
+ print("Error creating JSON data: \(error)")
681
+ hideLoadingIndicator()
682
+ return
683
+ }
684
+
685
+ let session = URLSession.shared
686
+ let task = session.dataTask(with: uRLRequest) { (serviceData, serviceResponse, error) in
687
+
688
+ DispatchQueue.main.async {
689
+ self.hideLoadingIndicator() // Stop loader when response is received
690
+ }
691
+
692
+ if let error = error {
693
+ self.presentPaymentErrorVC(errorMessage: error.localizedDescription)
694
+ return
695
+ }
696
+
697
+ guard let httpResponse = serviceResponse as? HTTPURLResponse else {
698
+ self.presentPaymentErrorVC(errorMessage: "Invalid response")
699
+ return
700
+ }
701
+
702
+ if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
703
+ if let data = serviceData {
704
+ do {
705
+ if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
706
+ print("Response Data: \(responseObject)")
707
+
708
+ // Check if status is 0 and handle the error
709
+ if let status = responseObject["status"] as? Int, status == 0 {
710
+ let errorMessage = responseObject["message"] as? String ?? "Unknown error"
711
+ self.presentPaymentErrorVC(errorMessage: errorMessage)
712
+ } else {
713
+ DispatchQueue.main.async {
714
+ if let paymentDoneVC = self.storyboard?.instantiateViewController(withIdentifier: "PaymentDoneVC") as? PaymentDoneVC {
715
+ paymentDoneVC.chargeData = responseObject
716
+ paymentDoneVC.selectedPaymentMethod = self.selectedPaymentMethod
717
+ paymentDoneVC.easyPayDelegate = self.easyPayDelegate
718
+ paymentDoneVC.request = self.request
719
+
720
+ // Pass billing info and additional info if available
721
+ if let billingInfoData = self.request.fields,
722
+ let fieldSection = try? JSONDecoder().decode(FieldSection.self, from: billingInfoData) {
723
+
724
+ // Filter billing info: only include non-empty values
725
+ let filteredBilling = fieldSection.billing.filter { !($0.value.trimmingCharacters(in: .whitespaces).isEmpty) }
726
+ paymentDoneVC.billingInfoData = filteredBilling
727
+ var billingDict: [String: Any] = [:]
728
+ filteredBilling.forEach { billingDict[$0.name] = $0.value }
729
+ paymentDoneVC.billingInfo = billingDict
730
+
731
+ // Filter additional info: only include non-empty values
732
+ let filteredAdditional = fieldSection.additional.filter { !($0.value.trimmingCharacters(in: .whitespaces).isEmpty) }
733
+ paymentDoneVC.additionalInfoData = filteredAdditional
734
+ var additionalDict: [String: Any] = [:]
735
+ filteredAdditional.forEach { additionalDict[$0.name] = $0.value }
736
+ paymentDoneVC.additionalInfo = additionalDict
737
+ }
738
+ self.navigationController?.pushViewController(paymentDoneVC, animated: true)
739
+ }
740
+ }
741
+ }
742
+ } else {
743
+ self.presentPaymentErrorVC(errorMessage: "Invalid JSON format")
744
+ }
745
+ } catch let jsonError {
746
+ self.presentPaymentErrorVC(errorMessage: "Error parsing JSON: \(jsonError)")
747
+ }
748
+ } else {
749
+ self.presentPaymentErrorVC(errorMessage: "No data received")
750
+ }
751
+ } else {
752
+ if let data = serviceData,
753
+ let responseObj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
754
+ let message = responseObj["message"] as? String {
755
+ self.presentPaymentErrorVC(errorMessage: message)
756
+ } else {
757
+ self.presentPaymentErrorVC(errorMessage: "HTTP Status Code: \(httpResponse.statusCode)")
758
+ }
759
+ }
760
+ }
761
+ task.resume()
762
+ }
763
+
764
+ //MARK: - Card Charge Api If billing info is nil and saved card
765
+ func paymentIntentWithSavedCardApi(customerId: String?) {
766
+ showLoadingIndicator()
767
+
768
+ let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.charges.path()
769
+
770
+ guard let serviceURL = URL(string: fullURL) else {
771
+ print("Invalid URL")
772
+ hideLoadingIndicator()
773
+ return
774
+ }
775
+
776
+ var urlRequest = URLRequest(url: serviceURL)
777
+ urlRequest.httpMethod = "POST"
778
+ urlRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
779
+
780
+ let token = UserStoreSingleton.shared.clientToken
781
+ print("Setting clientToken header: \(token ?? "None")")
782
+ urlRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
783
+
784
+ // Add API headers
785
+ if let apiKey = EnvironmentConfig.apiKey,
786
+ let apiSecret = EnvironmentConfig.apiSecret {
787
+ urlRequest.addValue(apiKey, forHTTPHeaderField: "x-api-key")
788
+ urlRequest.addValue(apiSecret, forHTTPHeaderField: "x-api-secret")
789
+ }
790
+
791
+ let emailPrefix = email?.components(separatedBy: "@").first ?? ""
792
+
793
+ var params: [String: Any] = [
794
+ "name": emailPrefix,
795
+ "card_number": cardNumber?.replacingOccurrences(of: " ", with: "") ?? "",
796
+ "cardholder_name": nameOnCard ?? "",
797
+ "exp_month": expiryDate?.components(separatedBy: "/").first ?? "",
798
+ "exp_year": expiryDate?.components(separatedBy: "/").last ?? "",
799
+ "cvc": cvv ?? "",
800
+ "description": "description",
801
+ "currency": "usd",
802
+ "payment_method": selectedPaymentMethod ?? "",
803
+ "save_card": 1,
804
+ "is_default": "1"
805
+ ]
806
+
807
+ if let customerId = customerId {
808
+ params["customer"] = customerId
809
+ params["customer_id"] = customerId
810
+ } else {
811
+ params["username"] = emailPrefix
812
+ params["email"] = email ?? ""
813
+ }
814
+
815
+ if customerId == nil {
816
+ params["create_customer"] = "1"
817
+ }
818
+
819
+ // Add these if recurring is enabled
820
+ if let req = request, req.is_recurring == true {
821
+ if let recurringType = req.recurringStartDateType, recurringType == .custom {
822
+ // Only send start_date if type is .custom and field is not empty
823
+ if let startDateText = startDate, !startDateText.isEmpty {
824
+ let inputFormatter = DateFormatter()
825
+ inputFormatter.dateFormat = "dd/MM/yyyy"
826
+
827
+ let outputFormatter = DateFormatter()
828
+ outputFormatter.dateFormat = "MM/dd/yyyy"
829
+
830
+ if let date = inputFormatter.date(from: startDateText) {
831
+ let apiFormattedDate = outputFormatter.string(from: date)
832
+ params["start_date"] = apiFormattedDate
833
+ } else {
834
+ print("Invalid date format in startDateText")
835
+ }
836
+ }
837
+ }
838
+
839
+ params["interval"] = chosenPlan?.lowercased()
840
+ }
841
+
842
+ // ✅ Include metadata only if it has at least 1 key-value pair
843
+ if let metadata = request?.metadata, !metadata.isEmpty {
844
+ params["metadata"] = metadata
845
+ }
846
+
847
+ print(params)
848
+
849
+ do {
850
+ let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
851
+ urlRequest.httpBody = jsonData
852
+ if let jsonString = String(data: jsonData, encoding: .utf8) {
853
+ print("JSON Payload: \(jsonString)")
854
+ }
855
+ } catch let error {
856
+ print("Error creating JSON data: \(error)")
857
+ hideLoadingIndicator()
858
+ return
859
+ }
860
+
861
+ let session = URLSession.shared
862
+ let task = session.dataTask(with: urlRequest) { (serviceData, serviceResponse, error) in
863
+
864
+ DispatchQueue.main.async {
865
+ self.hideLoadingIndicator() // Stop loader when response is received
866
+ }
867
+
868
+ if let error = error {
869
+ self.presentPaymentErrorVC(errorMessage: error.localizedDescription)
870
+ return
871
+ }
872
+
873
+ guard let httpResponse = serviceResponse as? HTTPURLResponse else {
874
+ self.presentPaymentErrorVC(errorMessage: "Invalid response")
875
+ return
876
+ }
877
+
878
+ if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
879
+ if let data = serviceData {
880
+ do {
881
+ if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
882
+ print("Response Data: \(responseObject)")
883
+
884
+ // Check if status is 0 and handle the error
885
+ if let status = responseObject["status"] as? Int, status == 0 {
886
+ let errorMessage = responseObject["message"] as? String ?? "Unknown error"
887
+ self.presentPaymentErrorVC(errorMessage: errorMessage)
888
+ } else {
889
+ DispatchQueue.main.async {
890
+ if let paymentDoneVC = self.storyboard?.instantiateViewController(withIdentifier: "PaymentDoneVC") as? PaymentDoneVC {
891
+ paymentDoneVC.chargeData = responseObject
892
+ paymentDoneVC.selectedPaymentMethod = self.selectedPaymentMethod
893
+ paymentDoneVC.easyPayDelegate = self.easyPayDelegate
894
+ paymentDoneVC.request = self.request
895
+ self.navigationController?.pushViewController(paymentDoneVC, animated: true)
896
+ }
897
+ }
898
+ }
899
+ } else {
900
+ self.presentPaymentErrorVC(errorMessage: "Invalid JSON format")
901
+ }
902
+ } catch let jsonError {
903
+ self.presentPaymentErrorVC(errorMessage: "Error parsing JSON: \(jsonError)")
904
+ }
905
+ } else {
906
+ self.presentPaymentErrorVC(errorMessage: "No data received")
907
+ }
908
+ } else {
909
+ if let data = serviceData,
910
+ let responseObj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
911
+ let message = responseObj["message"] as? String {
912
+ self.presentPaymentErrorVC(errorMessage: message)
913
+ } else {
914
+ self.presentPaymentErrorVC(errorMessage: "HTTP Status Code: \(httpResponse.statusCode)")
915
+ }
916
+ }
917
+ }
918
+ task.resume()
919
+ }
920
+
921
+ func presentPaymentErrorVC(errorMessage: String) {
922
+ DispatchQueue.main.async {
923
+ if let paymentErrorVC = self.storyboard?.instantiateViewController(withIdentifier: "PaymentErrorVC") as? PaymentErrorVC {
924
+ paymentErrorVC.errorMessage = errorMessage
925
+ paymentErrorVC.easyPayDelegate = self.easyPayDelegate // Pass the reference here
926
+ self.navigationController?.pushViewController(paymentErrorVC, animated: true)
927
+ }
928
+ }
929
+ }
930
+
931
+ //MARK: - Account Charge Api
932
+ func accountChargeApi(customerId: String?) {
933
+ showLoadingIndicator()
934
+
935
+ let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.achCharge.path()
936
+
937
+ guard let serviceURL = URL(string: fullURL) else {
938
+ print("Invalid URL")
939
+ hideLoadingIndicator()
940
+ return
941
+ }
942
+
943
+ var uRLRequest = URLRequest(url: serviceURL)
944
+ uRLRequest.httpMethod = "POST"
945
+ uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
946
+
947
+ let token = UserStoreSingleton.shared.clientToken
948
+ print("Setting clientToken header: \(token ?? "None")")
949
+ uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
950
+
951
+ // Add API headers
952
+ if let apiKey = EnvironmentConfig.apiKey,
953
+ let apiSecret = EnvironmentConfig.apiSecret {
954
+ uRLRequest.addValue(apiKey, forHTTPHeaderField: "x-api-key")
955
+ uRLRequest.addValue(apiSecret, forHTTPHeaderField: "x-api-secret")
956
+ }
957
+
958
+ let emailPrefix = email?.components(separatedBy: "@").first ?? ""
959
+
960
+ var params: [String: Any] = [
961
+ // "name": accountName ?? "",
962
+ "name": !(request.name?.isEmpty ?? true) ? request.name! : (accountName ?? ""),
963
+ "email": userEmail ?? "",
964
+ "currency": "usd",
965
+ "account_type": accountType?.lowercased() ?? "",
966
+ "routing_number": routingNumber ?? "",
967
+ "account_number": accountNumber ?? "",
968
+ "payment_mode": "auth_and_capture",
969
+ "payment_intent": UserStoreSingleton.shared.paymentIntent ?? "",
970
+ "levelIndicator": 1,
971
+ "save_account": 1,
972
+ "payment_method": "ach"
973
+ ]
974
+
975
+ if let customerId = customerId {
976
+ params["customer"] = customerId
977
+ } else {
978
+ params["username"] = emailPrefix
979
+ }
980
+
981
+ if customerId == nil {
982
+ params["create_customer"] = "1"
983
+ }
984
+
985
+ if let billingInfoData = request.fields {
986
+ do {
987
+ let fieldSection = try JSONDecoder().decode(FieldSection.self, from: billingInfoData)
988
+
989
+ // Billing Info
990
+ let billing = fieldSection.billing
991
+ if !billing.isEmpty {
992
+ var billingDict: [String: Any] = [:]
993
+ billing.forEach { billingDict[$0.name] = $0.value }
994
+
995
+ if let address = billingDict["address"] as? String, !address.isEmpty {
996
+ params["address"] = address
997
+ }
998
+ if let country = billingDict["country"] as? String, !country.isEmpty {
999
+ params["country"] = country
1000
+ }
1001
+ if let state = billingDict["state"] as? String, !state.isEmpty {
1002
+ params["state"] = state
1003
+ }
1004
+ if let city = billingDict["city"] as? String, !city.isEmpty {
1005
+ params["city"] = city
1006
+ }
1007
+ if let postalCode = billingDict["postal_code"] as? String, !postalCode.isEmpty {
1008
+ params["zip"] = postalCode
1009
+ }
1010
+ }
1011
+
1012
+ // Additional Info
1013
+ let additional = fieldSection.additional
1014
+ if !additional.isEmpty {
1015
+ var additionalDict: [String: Any] = [:]
1016
+ additional.forEach { additionalDict[$0.name] = $0.value }
1017
+
1018
+ if let desc = additionalDict["description"] as? String, !desc.isEmpty {
1019
+ params["description"] = desc
1020
+ } else {
1021
+ params["description"] = "Hosted payment checkout"
1022
+ }
1023
+
1024
+ if let phone = additionalDict["phone_number"] as? String, !phone.isEmpty {
1025
+ params["phone_number"] = phone
1026
+ }
1027
+ if let email = additionalDict["email"] as? String, !email.isEmpty {
1028
+ params["email"] = email
1029
+ }
1030
+ if let name = additionalDict["name"] as? String, !name.isEmpty {
1031
+ params["name"] = name
1032
+ }
1033
+ } else {
1034
+ // If no description in additional info, set default
1035
+ params["description"] = "Hosted payment checkout"
1036
+ }
1037
+
1038
+ } catch {
1039
+ print("Failed to decode FieldSection: \(error)")
1040
+ params["description"] = "Hosted payment checkout"
1041
+ }
1042
+ } else {
1043
+ // Fallback if billingInfoData is missing
1044
+ params["description"] = "Hosted payment checkout"
1045
+ }
1046
+
1047
+ // Add these if recurring is enabled
1048
+ if let req = request, req.is_recurring == true {
1049
+ if let recurringType = req.recurringStartDateType, recurringType == .custom {
1050
+ // Only send start_date if type is .custom and field is not empty
1051
+ if let startDateText = startDate, !startDateText.isEmpty {
1052
+ let inputFormatter = DateFormatter()
1053
+ inputFormatter.dateFormat = "dd/MM/yyyy"
1054
+
1055
+ let outputFormatter = DateFormatter()
1056
+ outputFormatter.dateFormat = "MM/dd/yyyy"
1057
+
1058
+ if let date = inputFormatter.date(from: startDateText) {
1059
+ let apiFormattedDate = outputFormatter.string(from: date)
1060
+ params["start_date"] = apiFormattedDate
1061
+ } else {
1062
+ print("Invalid date format in startDateText")
1063
+ }
1064
+ }
1065
+ }
1066
+
1067
+ params["interval"] = chosenPlan?.lowercased()
1068
+ }
1069
+
1070
+ // ✅ Include metadata only if it has at least 1 key-value pair
1071
+ if let metadata = request?.metadata, !metadata.isEmpty {
1072
+ params["metadata"] = metadata
1073
+ }
1074
+
1075
+ print(params)
1076
+
1077
+ do {
1078
+ let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
1079
+ uRLRequest.httpBody = jsonData
1080
+ if let jsonString = String(data: jsonData, encoding: .utf8) {
1081
+ print("JSON Payload: \(jsonString)")
1082
+ }
1083
+ } catch let error {
1084
+ print("Error creating JSON data: \(error)")
1085
+ hideLoadingIndicator()
1086
+ return
1087
+ }
1088
+
1089
+ let session = URLSession.shared
1090
+ let task = session.dataTask(with: uRLRequest) { (serviceData, serviceResponse, error) in
1091
+
1092
+ DispatchQueue.main.async {
1093
+ self.hideLoadingIndicator() // Stop loader when response is received
1094
+ }
1095
+
1096
+ if let error = error {
1097
+ self.presentPaymentErrorVC(errorMessage: error.localizedDescription)
1098
+ return
1099
+ }
1100
+
1101
+ guard let httpResponse = serviceResponse as? HTTPURLResponse else {
1102
+ self.presentPaymentErrorVC(errorMessage: "Invalid response")
1103
+ return
1104
+ }
1105
+
1106
+ if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
1107
+ if let data = serviceData {
1108
+ do {
1109
+ if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
1110
+ print("Response Data: \(responseObject)")
1111
+
1112
+ // Check if status is 0 and handle the error
1113
+ if let status = responseObject["status"] as? Int, status == 0 {
1114
+ let errorMessage = responseObject["message"] as? String ?? "Unknown error"
1115
+ self.presentPaymentErrorVC(errorMessage: errorMessage)
1116
+ } else {
1117
+ DispatchQueue.main.async {
1118
+ if let paymentDoneVC = self.storyboard?.instantiateViewController(withIdentifier: "PaymentDoneVC") as? PaymentDoneVC {
1119
+ paymentDoneVC.chargeData = responseObject
1120
+ paymentDoneVC.selectedPaymentMethod = self.selectedPaymentMethod
1121
+ paymentDoneVC.easyPayDelegate = self.easyPayDelegate
1122
+ paymentDoneVC.bankPaymentParams = params
1123
+ paymentDoneVC.request = self.request
1124
+ // Pass billing info and additional info if available
1125
+ if let billingInfoData = self.request.fields,
1126
+ let fieldSection = try? JSONDecoder().decode(FieldSection.self, from: billingInfoData) {
1127
+
1128
+ // Filter billing info: only include non-empty values
1129
+ let filteredBilling = fieldSection.billing.filter { !($0.value.trimmingCharacters(in: .whitespaces).isEmpty) }
1130
+ paymentDoneVC.billingInfoData = filteredBilling
1131
+ var billingDict: [String: Any] = [:]
1132
+ filteredBilling.forEach { billingDict[$0.name] = $0.value }
1133
+ paymentDoneVC.billingInfo = billingDict
1134
+
1135
+ // Filter additional info: only include non-empty values
1136
+ let filteredAdditional = fieldSection.additional.filter { !($0.value.trimmingCharacters(in: .whitespaces).isEmpty) }
1137
+ paymentDoneVC.additionalInfoData = filteredAdditional
1138
+ var additionalDict: [String: Any] = [:]
1139
+ filteredAdditional.forEach { additionalDict[$0.name] = $0.value }
1140
+ paymentDoneVC.additionalInfo = additionalDict
1141
+ }
1142
+ self.navigationController?.pushViewController(paymentDoneVC, animated: true)
1143
+ }
1144
+ }
1145
+ }
1146
+ } else {
1147
+ self.presentPaymentErrorVC(errorMessage: "Invalid JSON format")
1148
+ }
1149
+ } catch let jsonError {
1150
+ self.presentPaymentErrorVC(errorMessage: "Error parsing JSON: \(jsonError)")
1151
+ }
1152
+ } else {
1153
+ self.presentPaymentErrorVC(errorMessage: "No data received")
1154
+ }
1155
+ } else {
1156
+ if let data = serviceData,
1157
+ let responseObj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
1158
+ let message = responseObj["message"] as? String {
1159
+ self.presentPaymentErrorVC(errorMessage: message)
1160
+ } else {
1161
+ self.presentPaymentErrorVC(errorMessage: "HTTP Status Code: \(httpResponse.statusCode)")
1162
+ }
1163
+ }
1164
+ }
1165
+ task.resume()
1166
+ }
1167
+
1168
+ //MARK: - Account Charge Api if billing info is nil and saved account
1169
+ func accountChargeWithSavedAccountApi(customerId: String?) {
1170
+ showLoadingIndicator()
1171
+
1172
+ let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.achCharge.path()
1173
+
1174
+ guard let serviceURL = URL(string: fullURL) else {
1175
+ print("Invalid URL")
1176
+ hideLoadingIndicator()
1177
+ return
1178
+ }
1179
+
1180
+ var uRLRequest = URLRequest(url: serviceURL)
1181
+ uRLRequest.httpMethod = "POST"
1182
+ uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
1183
+
1184
+ let token = UserStoreSingleton.shared.clientToken
1185
+ print("Setting clientToken header: \(token ?? "None")")
1186
+ uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
1187
+
1188
+ // Add API headers
1189
+ if let apiKey = EnvironmentConfig.apiKey,
1190
+ let apiSecret = EnvironmentConfig.apiSecret {
1191
+ uRLRequest.addValue(apiKey, forHTTPHeaderField: "x-api-key")
1192
+ uRLRequest.addValue(apiSecret, forHTTPHeaderField: "x-api-secret")
1193
+ }
1194
+
1195
+ let emailPrefix = email?.components(separatedBy: "@").first ?? ""
1196
+
1197
+ var params: [String: Any] = [
1198
+ // "name": emailPrefix,
1199
+ "name": !(request.name?.isEmpty ?? true) ? request.name! : (emailPrefix),
1200
+ "email": email ?? "",
1201
+ "description": "Test Description",
1202
+ "currency": "usd",
1203
+ "account_type": accountType?.lowercased() ?? "",
1204
+ "routing_number": routingNumber ?? "",
1205
+ "account_number": accountNumber ?? "",
1206
+ "payment_mode": "auth_and_capture",
1207
+ "payment_intent": UserStoreSingleton.shared.paymentIntent ?? "",
1208
+ "levelIndicator": 1,
1209
+ "save_account": 1,
1210
+ "payment_method": "ach"
1211
+ ]
1212
+
1213
+ if let customerId = customerId {
1214
+ params["customer"] = customerId
1215
+ } else {
1216
+ params["username"] = emailPrefix
1217
+ }
1218
+
1219
+ if customerId == nil {
1220
+ params["create_customer"] = "1"
1221
+ }
1222
+
1223
+ // Add these if recurring is enabled
1224
+ if let req = request, req.is_recurring == true {
1225
+ if let recurringType = req.recurringStartDateType, recurringType == .custom {
1226
+ // Only send start_date if type is .custom and field is not empty
1227
+ if let startDateText = startDate, !startDateText.isEmpty {
1228
+ let inputFormatter = DateFormatter()
1229
+ inputFormatter.dateFormat = "dd/MM/yyyy"
1230
+
1231
+ let outputFormatter = DateFormatter()
1232
+ outputFormatter.dateFormat = "MM/dd/yyyy"
1233
+
1234
+ if let date = inputFormatter.date(from: startDateText) {
1235
+ let apiFormattedDate = outputFormatter.string(from: date)
1236
+ params["start_date"] = apiFormattedDate
1237
+ } else {
1238
+ print("Invalid date format in startDateText")
1239
+ }
1240
+ }
1241
+ }
1242
+
1243
+ params["interval"] = chosenPlan?.lowercased()
1244
+ }
1245
+
1246
+ // ✅ Include metadata only if it has at least 1 key-value pair
1247
+ if let metadata = request?.metadata, !metadata.isEmpty {
1248
+ params["metadata"] = metadata
1249
+ }
1250
+
1251
+ print(params)
1252
+
1253
+ do {
1254
+ let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
1255
+ uRLRequest.httpBody = jsonData
1256
+ if let jsonString = String(data: jsonData, encoding: .utf8) {
1257
+ print("JSON Payload: \(jsonString)")
1258
+ }
1259
+ } catch let error {
1260
+ print("Error creating JSON data: \(error)")
1261
+ hideLoadingIndicator()
1262
+ return
1263
+ }
1264
+
1265
+ let session = URLSession.shared
1266
+ let task = session.dataTask(with: uRLRequest) { (serviceData, serviceResponse, error) in
1267
+
1268
+ DispatchQueue.main.async {
1269
+ self.hideLoadingIndicator() // Stop loader when response is received
1270
+ }
1271
+
1272
+ if let error = error {
1273
+ self.presentPaymentErrorVC(errorMessage: error.localizedDescription)
1274
+ return
1275
+ }
1276
+
1277
+ guard let httpResponse = serviceResponse as? HTTPURLResponse else {
1278
+ self.presentPaymentErrorVC(errorMessage: "Invalid response")
1279
+ return
1280
+ }
1281
+
1282
+ if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
1283
+ if let data = serviceData {
1284
+ do {
1285
+ if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
1286
+ print("Response Data: \(responseObject)")
1287
+
1288
+ // Check if status is 0 and handle the error
1289
+ if let status = responseObject["status"] as? Int, status == 0 {
1290
+ let errorMessage = responseObject["message"] as? String ?? "Unknown error"
1291
+ self.presentPaymentErrorVC(errorMessage: errorMessage)
1292
+ } else {
1293
+ DispatchQueue.main.async {
1294
+ if let paymentDoneVC = self.storyboard?.instantiateViewController(withIdentifier: "PaymentDoneVC") as? PaymentDoneVC {
1295
+ paymentDoneVC.chargeData = responseObject
1296
+ paymentDoneVC.selectedPaymentMethod = self.selectedPaymentMethod
1297
+ paymentDoneVC.easyPayDelegate = self.easyPayDelegate
1298
+ paymentDoneVC.bankPaymentParams = params
1299
+ paymentDoneVC.request = self.request
1300
+ self.navigationController?.pushViewController(paymentDoneVC, animated: true)
1301
+ }
1302
+ }
1303
+ }
1304
+ } else {
1305
+ self.presentPaymentErrorVC(errorMessage: "Invalid JSON format")
1306
+ }
1307
+ } catch let jsonError {
1308
+ self.presentPaymentErrorVC(errorMessage: "Error parsing JSON: \(jsonError)")
1309
+ }
1310
+ } else {
1311
+ self.presentPaymentErrorVC(errorMessage: "No data received")
1312
+ }
1313
+ } else {
1314
+ if let data = serviceData,
1315
+ let responseObj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
1316
+ let message = responseObj["message"] as? String {
1317
+ self.presentPaymentErrorVC(errorMessage: message)
1318
+ } else {
1319
+ self.presentPaymentErrorVC(errorMessage: "HTTP Status Code: \(httpResponse.statusCode)")
1320
+ }
1321
+ }
1322
+ }
1323
+ task.resume()
1324
+ }
1325
+
1326
+ //MARK: - GrailPay Account Charge Api if user saved account
1327
+ func grailPayAccountChargeApi(customerId: String?) {
1328
+ showLoadingIndicator()
1329
+
1330
+ let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.achCharge.path()
1331
+
1332
+ guard let serviceURL = URL(string: fullURL) else {
1333
+ print("Invalid URL")
1334
+ hideLoadingIndicator()
1335
+ return
1336
+ }
1337
+
1338
+ var uRLRequest = URLRequest(url: serviceURL)
1339
+ uRLRequest.httpMethod = "POST"
1340
+ uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
1341
+
1342
+ let token = UserStoreSingleton.shared.clientToken
1343
+ print("Setting clientToken header: \(token ?? "None")")
1344
+ uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
1345
+
1346
+ if let apiKey = EnvironmentConfig.apiKey {
1347
+ uRLRequest.addValue(apiKey, forHTTPHeaderField: "X-Api-Key")
1348
+ }
1349
+ if let apiSecret = EnvironmentConfig.apiSecret {
1350
+ uRLRequest.addValue(apiSecret, forHTTPHeaderField: "X-Api-Secret")
1351
+ }
1352
+
1353
+ let emailPrefix = userEmail?.components(separatedBy: "@").first ?? ""
1354
+
1355
+ var params: [String: Any] = [
1356
+ "account_id": self.grailPayAccountID ?? "",
1357
+ "account_type": self.selectedGrailPayAccountType ?? "",
1358
+ "name": self.selectedGrailPayAccountName ?? "",
1359
+ "save_account": 1,
1360
+ "is_default": 1,
1361
+ "customer_id": customerId ?? "",
1362
+ "email": userEmail ?? "",
1363
+ "create_customer": "1",
1364
+ ]
1365
+
1366
+ if let customerId = customerId {
1367
+ params["customer"] = customerId
1368
+ } else {
1369
+ params["username"] = emailPrefix
1370
+ }
1371
+
1372
+ if customerId == nil {
1373
+ params["create_customer"] = "1"
1374
+ }
1375
+
1376
+ // // Billing Info
1377
+ // if let visibility = visibility, visibility.billing == true,
1378
+ // let billing = billingInfo, !billing.isEmpty {
1379
+ // var billingDict: [String: Any] = [:]
1380
+ // billing.forEach { billingDict[$0.name] = $0.value }
1381
+ //
1382
+ // params["address"] = billingDict["address"] as? String ?? ""
1383
+ // params["country"] = billingDict["country"] as? String ?? ""
1384
+ // params["state"] = billingDict["state"] as? String ?? ""
1385
+ // params["city"] = billingDict["city"] as? String ?? ""
1386
+ // params["zip"] = billingDict["postal_code"] as? String ?? ""
1387
+ // }
1388
+
1389
+ // Always include Billing Info if available
1390
+ if let billing = billingInfo, !billing.isEmpty {
1391
+ var billingDict: [String: Any] = [:]
1392
+ billing.forEach { billingDict[$0.name] = $0.value }
1393
+
1394
+ params["address"] = billingDict["address"] as? String ?? ""
1395
+ params["country"] = billingDict["country"] as? String ?? ""
1396
+ params["state"] = billingDict["state"] as? String ?? ""
1397
+ params["city"] = billingDict["city"] as? String ?? ""
1398
+ params["zip"] = billingDict["postal_code"] as? String ?? ""
1399
+ }
1400
+
1401
+ // // Additional Info or default description
1402
+ // var descriptionValue: String = "Hosted payment checkout" // default
1403
+ // if let visibility = visibility, visibility.additional == true,
1404
+ // let additional = additionalInfo, !additional.isEmpty {
1405
+ //
1406
+ // var additionalDict: [String: Any] = [:]
1407
+ // additional.forEach { additionalDict[$0.name] = $0.value }
1408
+ //
1409
+ // if let desc = additionalDict["description"] as? String, !desc.isEmpty {
1410
+ // descriptionValue = desc
1411
+ // }
1412
+ //
1413
+ // if let phone = additionalDict["phone_number"] as? String, !phone.isEmpty {
1414
+ // params["phone_number"] = phone
1415
+ // }
1416
+ // }
1417
+ // params["description"] = descriptionValue
1418
+
1419
+ // Always include Additional Info if available
1420
+ var descriptionValue: String = "Hosted payment checkout"
1421
+ if let additional = additionalInfo, !additional.isEmpty {
1422
+ var additionalDict: [String: Any] = [:]
1423
+ additional.forEach { additionalDict[$0.name] = $0.value }
1424
+
1425
+ if let desc = additionalDict["description"] as? String, !desc.isEmpty {
1426
+ descriptionValue = desc
1427
+ }
1428
+
1429
+ if let phone = additionalDict["phone_number"] as? String, !phone.isEmpty {
1430
+ params["phone_number"] = phone
1431
+ }
1432
+ }
1433
+ params["description"] = descriptionValue
1434
+
1435
+ // Add these if recurring is enabled
1436
+ if let req = request, req.is_recurring == true {
1437
+ if let recurringType = req.recurringStartDateType, recurringType == .custom {
1438
+ // Only send start_date if type is .custom and field is not empty
1439
+ if let startDateText = startDate, !startDateText.isEmpty {
1440
+ let inputFormatter = DateFormatter()
1441
+ inputFormatter.dateFormat = "dd/MM/yyyy"
1442
+
1443
+ let outputFormatter = DateFormatter()
1444
+ outputFormatter.dateFormat = "MM/dd/yyyy"
1445
+
1446
+ if let date = inputFormatter.date(from: startDateText) {
1447
+ let apiFormattedDate = outputFormatter.string(from: date)
1448
+ params["start_date"] = apiFormattedDate
1449
+ } else {
1450
+ print("Invalid date format in startDateText")
1451
+ }
1452
+ }
1453
+ }
1454
+
1455
+ params["interval"] = chosenPlan?.lowercased()
1456
+ }
1457
+
1458
+ // ✅ Include metadata only if it has at least 1 key-value pair
1459
+ if let metadata = request?.metadata, !metadata.isEmpty {
1460
+ params["metadata"] = metadata
1461
+ }
1462
+
1463
+ print(params)
1464
+
1465
+ do {
1466
+ let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
1467
+ uRLRequest.httpBody = jsonData
1468
+ if let jsonString = String(data: jsonData, encoding: .utf8) {
1469
+ print("JSON Payload: \(jsonString)")
1470
+ }
1471
+ } catch let error {
1472
+ print("Error creating JSON data: \(error)")
1473
+ hideLoadingIndicator()
1474
+ return
1475
+ }
1476
+
1477
+ let session = URLSession.shared
1478
+ let task = session.dataTask(with: uRLRequest) { (serviceData, serviceResponse, error) in
1479
+
1480
+ DispatchQueue.main.async {
1481
+ self.hideLoadingIndicator() // Stop loader when response is received
1482
+ }
1483
+
1484
+ if let error = error {
1485
+ self.presentPaymentErrorVC(errorMessage: error.localizedDescription)
1486
+ return
1487
+ }
1488
+
1489
+ guard let httpResponse = serviceResponse as? HTTPURLResponse else {
1490
+ self.presentPaymentErrorVC(errorMessage: "Invalid response")
1491
+ return
1492
+ }
1493
+
1494
+ if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
1495
+ if let data = serviceData {
1496
+ do {
1497
+ if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
1498
+ print("Response Data: \(responseObject)")
1499
+
1500
+ // Check if status is 0 and handle the error
1501
+ if let status = responseObject["status"] as? Int, status == 0 {
1502
+ let errorMessage = responseObject["message"] as? String ?? "Unknown error"
1503
+ self.presentPaymentErrorVC(errorMessage: errorMessage)
1504
+ } else {
1505
+ DispatchQueue.main.async {
1506
+ if let paymentDoneVC = self.storyboard?.instantiateViewController(withIdentifier: "PaymentDoneVC") as? PaymentDoneVC {
1507
+ paymentDoneVC.chargeData = responseObject
1508
+ paymentDoneVC.selectedPaymentMethod = self.selectedPaymentMethod
1509
+ paymentDoneVC.easyPayDelegate = self.easyPayDelegate
1510
+ paymentDoneVC.bankPaymentParams = params
1511
+ // Pass billing and additional info
1512
+ // Conditionally pass raw FieldItem array
1513
+ paymentDoneVC.visibility = self.visibility
1514
+ paymentDoneVC.request = self.request
1515
+
1516
+ // if self.visibility?.billing == true {
1517
+ paymentDoneVC.billingInfoData = self.billingInfo
1518
+ var billingDict: [String: Any] = [:]
1519
+ self.billingInfo?.forEach { billingDict[$0.name] = $0.value }
1520
+ paymentDoneVC.billingInfo = billingDict
1521
+ // }
1522
+
1523
+ // if self.visibility?.additional == true {
1524
+ paymentDoneVC.additionalInfoData = self.additionalInfo
1525
+ var additionalDict: [String: Any] = [:]
1526
+ self.additionalInfo?.forEach { additionalDict[$0.name] = $0.value }
1527
+ paymentDoneVC.additionalInfo = additionalDict
1528
+ // }
1529
+ self.navigationController?.pushViewController(paymentDoneVC, animated: true)
1530
+ }
1531
+ }
1532
+ }
1533
+ } else {
1534
+ self.presentPaymentErrorVC(errorMessage: "Invalid JSON format")
1535
+ }
1536
+ } catch let jsonError {
1537
+ self.presentPaymentErrorVC(errorMessage: "Error parsing JSON: \(jsonError)")
1538
+ }
1539
+ } else {
1540
+ self.presentPaymentErrorVC(errorMessage: "No data received")
1541
+ }
1542
+ } else {
1543
+ if let data = serviceData,
1544
+ let responseObj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
1545
+ let message = responseObj["message"] as? String {
1546
+ self.presentPaymentErrorVC(errorMessage: message)
1547
+ } else {
1548
+ self.presentPaymentErrorVC(errorMessage: "HTTP Status Code: \(httpResponse.statusCode)")
1549
+ }
1550
+ }
1551
+ }
1552
+ task.resume()
1553
+ }
1554
+
1555
+ // MARK: - 3DS Functionality
1556
+
1557
+ // MARK: - Credit Card Charge Api If Billing info is nil and Without Login.
1558
+ func threeDSecurePaymentApi(customerId: String?) {
1559
+ showLoadingIndicator()
1560
+
1561
+ let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.threeDSecure.path()
1562
+
1563
+ guard let serviceURL = URL(string: fullURL) else {
1564
+ print("Invalid URL")
1565
+ hideLoadingIndicator()
1566
+ return
1567
+ }
1568
+
1569
+ var uRLRequest = URLRequest(url: serviceURL)
1570
+ uRLRequest.httpMethod = "POST"
1571
+ uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
1572
+
1573
+ let token = UserStoreSingleton.shared.clientToken
1574
+ print("Setting clientToken header: \(token ?? "None")")
1575
+ uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
1576
+
1577
+ // Add API headers
1578
+ if let apiKey = EnvironmentConfig.apiKey,
1579
+ let apiSecret = EnvironmentConfig.apiSecret {
1580
+ uRLRequest.addValue(apiKey, forHTTPHeaderField: "x-api-key")
1581
+ uRLRequest.addValue(apiSecret, forHTTPHeaderField: "x-api-secret")
1582
+ }
1583
+
1584
+ var params: [String: Any] = [
1585
+ "name": nameOnCard ?? "",
1586
+ "email": userEmail ?? "",
1587
+ "card_number": cardNumber?.replacingOccurrences(of: " ", with: "") ?? "",
1588
+ "cardholder_name": nameOnCard ?? "",
1589
+ "exp_month": expiryDate?.components(separatedBy: "/").first ?? "",
1590
+ "exp_year": expiryDate?.components(separatedBy: "/").last ?? "",
1591
+ "cvc": cvv ?? "",
1592
+ "description": "payment checkout",
1593
+ "currency": "usd",
1594
+ "payment_method": "card",
1595
+ "tokenize": request.tokenOnly ?? false,
1596
+ "save_card": 1,
1597
+ "is_default": "1",
1598
+ "customer_id": customerId ?? ""
1599
+ ]
1600
+
1601
+ if customerId == nil {
1602
+ params["create_customer"] = "1"
1603
+ }
1604
+
1605
+ // Add these if recurring is enabled
1606
+ if let req = request, req.is_recurring == true {
1607
+ if let recurringType = req.recurringStartDateType, recurringType == .custom {
1608
+ // Only send start_date if type is .custom and field is not empty
1609
+ if let startDateText = startDate, !startDateText.isEmpty {
1610
+ let inputFormatter = DateFormatter()
1611
+ inputFormatter.dateFormat = "dd/MM/yyyy"
1612
+
1613
+ let outputFormatter = DateFormatter()
1614
+ outputFormatter.dateFormat = "MM/dd/yyyy"
1615
+
1616
+ if let date = inputFormatter.date(from: startDateText) {
1617
+ let apiFormattedDate = outputFormatter.string(from: date)
1618
+ params["start_date"] = apiFormattedDate
1619
+ } else {
1620
+ print("Invalid date format in startDateText")
1621
+ }
1622
+ }
1623
+ }
1624
+
1625
+ params["interval"] = chosenPlan?.lowercased()
1626
+ }
1627
+
1628
+ // ✅ Include metadata only if it has at least 1 key-value pair
1629
+ if let metadata = request?.metadata, !metadata.isEmpty {
1630
+ params["metadata"] = metadata
1631
+ }
1632
+
1633
+ print(params)
1634
+
1635
+ do {
1636
+ let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
1637
+ uRLRequest.httpBody = jsonData
1638
+ if let jsonString = String(data: jsonData, encoding: .utf8) {
1639
+ print("JSON Payload: \(jsonString)")
1640
+ }
1641
+ } catch let error {
1642
+ print("Error creating JSON data: \(error)")
1643
+ hideLoadingIndicator()
1644
+ return
1645
+ }
1646
+
1647
+ let session = URLSession.shared
1648
+ let task = session.dataTask(with: uRLRequest) { (serviceData, serviceResponse, error) in
1649
+
1650
+ DispatchQueue.main.async {
1651
+ self.hideLoadingIndicator() // Stop loader when response is received
1652
+ }
1653
+
1654
+ if let error = error {
1655
+ print("Error: \(error.localizedDescription)")
1656
+ return
1657
+ }
1658
+
1659
+ guard let httpResponse = serviceResponse as? HTTPURLResponse else {
1660
+ print("Invalid response")
1661
+ return
1662
+ }
1663
+
1664
+ if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
1665
+ if let data = serviceData {
1666
+ do {
1667
+ if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
1668
+ print("Response Data: \(responseObject)")
1669
+
1670
+ // Check if status is 0 and handle the error
1671
+ if let status = responseObject["status"] as? Int, status == 0 {
1672
+ let errorMessage = responseObject["message"] as? String ?? "Unknown error"
1673
+ self.presentPaymentErrorVC(errorMessage: errorMessage)
1674
+ } else {
1675
+ DispatchQueue.main.async {
1676
+ if let paymentDoneVC = self.storyboard?.instantiateViewController(withIdentifier: "ThreeDSecurePaymentDoneVC") as? ThreeDSecurePaymentDoneVC {
1677
+
1678
+ let urlString = responseObject["redirect_url"] as? String ?? responseObject["location_url"] as? String ?? ""
1679
+ paymentDoneVC.redirectURL = urlString
1680
+ paymentDoneVC.chargeData = responseObject
1681
+ paymentDoneVC.easyPayDelegate = self.easyPayDelegate
1682
+ paymentDoneVC.cardApiParams = params
1683
+ paymentDoneVC.request = self.request
1684
+ self.navigationController?.pushViewController(paymentDoneVC, animated: true)
1685
+ }
1686
+ }
1687
+ }
1688
+ } else {
1689
+ self.presentPaymentErrorVC(errorMessage: "Invalid JSON format")
1690
+ }
1691
+ } catch let jsonError {
1692
+ self.presentPaymentErrorVC(errorMessage: "Error parsing JSON: \(jsonError)")
1693
+ }
1694
+ } else {
1695
+ self.presentPaymentErrorVC(errorMessage: "No data received")
1696
+ }
1697
+ } else {
1698
+ if let data = serviceData,
1699
+ let responseObj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
1700
+ let message = responseObj["message"] as? String {
1701
+ self.presentPaymentErrorVC(errorMessage: message)
1702
+ } else {
1703
+ self.presentPaymentErrorVC(errorMessage: "HTTP Status Code: \(httpResponse.statusCode)")
1704
+ }
1705
+ }
1706
+ }
1707
+ task.resume()
1708
+ }
1709
+
1710
+ // MARK: - Credit Card Charge Api If Billing info is not nil and Without Login.
1711
+ func threeDSecurePaymentSavedCardApi(customerId: String?) {
1712
+ showLoadingIndicator()
1713
+
1714
+ let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.threeDSecure.path()
1715
+
1716
+ guard let serviceURL = URL(string: fullURL) else {
1717
+ print("Invalid URL")
1718
+ hideLoadingIndicator()
1719
+ return
1720
+ }
1721
+
1722
+ var uRLRequest = URLRequest(url: serviceURL)
1723
+ uRLRequest.httpMethod = "POST"
1724
+ uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
1725
+
1726
+ let token = UserStoreSingleton.shared.clientToken
1727
+ print("Setting clientToken header: \(token ?? "None")")
1728
+ uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
1729
+
1730
+ // Add API headers
1731
+ if let apiKey = EnvironmentConfig.apiKey,
1732
+ let apiSecret = EnvironmentConfig.apiSecret {
1733
+ uRLRequest.addValue(apiKey, forHTTPHeaderField: "x-api-key")
1734
+ uRLRequest.addValue(apiSecret, forHTTPHeaderField: "x-api-secret")
1735
+ }
1736
+
1737
+ var params: [String: Any] = [
1738
+ "name": nameOnCard ?? "",
1739
+ "email": email ?? "",
1740
+ "card_number": cardNumber?.replacingOccurrences(of: " ", with: "") ?? "",
1741
+ "cardholder_name": nameOnCard ?? "",
1742
+ "exp_month": expiryDate?.components(separatedBy: "/").first ?? "",
1743
+ "exp_year": expiryDate?.components(separatedBy: "/").last ?? "",
1744
+ "cvc": cvv ?? "",
1745
+ "currency": "usd",
1746
+ "tokenize": request.tokenOnly ?? false,
1747
+ "save_card": "1",
1748
+ "is_default": "1",
1749
+ "customer_id": customerId ?? "",
1750
+ "customer" : customerId ?? ""
1751
+ ]
1752
+
1753
+ if customerId == nil {
1754
+ params["create_customer"] = "1"
1755
+ }
1756
+
1757
+ // Always include Billing Info if available
1758
+ if let billing = billingInfo, !billing.isEmpty {
1759
+ var billingDict: [String: Any] = [:]
1760
+ billing.forEach { billingDict[$0.name] = $0.value }
1761
+
1762
+ params["address"] = billingDict["address"] as? String ?? ""
1763
+ params["country"] = billingDict["country"] as? String ?? ""
1764
+ params["state"] = billingDict["state"] as? String ?? ""
1765
+ params["city"] = billingDict["city"] as? String ?? ""
1766
+ params["zip"] = billingDict["postal_code"] as? String ?? ""
1767
+ }
1768
+
1769
+ // Always include Additional Info if available
1770
+ var descriptionValue: String = "Hosted payment checkout"
1771
+ if let additional = additionalInfo, !additional.isEmpty {
1772
+ var additionalDict: [String: Any] = [:]
1773
+ additional.forEach { additionalDict[$0.name] = $0.value }
1774
+
1775
+ if let desc = additionalDict["description"] as? String, !desc.isEmpty {
1776
+ descriptionValue = desc
1777
+ }
1778
+
1779
+ if let phone = additionalDict["phone_number"] as? String, !phone.isEmpty {
1780
+ params["phone_number"] = phone
1781
+ }
1782
+ }
1783
+ params["description"] = descriptionValue
1784
+
1785
+ // Add these if recurring is enabled
1786
+ if let req = request, req.is_recurring == true {
1787
+ if let recurringType = req.recurringStartDateType, recurringType == .custom {
1788
+ // Only send start_date if type is .custom and field is not empty
1789
+ if let startDateText = startDate, !startDateText.isEmpty {
1790
+ let inputFormatter = DateFormatter()
1791
+ inputFormatter.dateFormat = "dd/MM/yyyy"
1792
+
1793
+ let outputFormatter = DateFormatter()
1794
+ outputFormatter.dateFormat = "MM/dd/yyyy"
1795
+
1796
+ if let date = inputFormatter.date(from: startDateText) {
1797
+ let apiFormattedDate = outputFormatter.string(from: date)
1798
+ params["start_date"] = apiFormattedDate
1799
+ } else {
1800
+ print("Invalid date format in startDateText")
1801
+ }
1802
+ }
1803
+ }
1804
+
1805
+ params["interval"] = chosenPlan?.lowercased()
1806
+ }
1807
+
1808
+ // ✅ Include metadata only if it has at least 1 key-value pair
1809
+ if let metadata = request?.metadata, !metadata.isEmpty {
1810
+ params["metadata"] = metadata
1811
+ }
1812
+
1813
+ print(params)
1814
+
1815
+ do {
1816
+ let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
1817
+ uRLRequest.httpBody = jsonData
1818
+ if let jsonString = String(data: jsonData, encoding: .utf8) {
1819
+ print("JSON Payload: \(jsonString)")
1820
+ }
1821
+ } catch let error {
1822
+ print("Error creating JSON data: \(error)")
1823
+ hideLoadingIndicator()
1824
+ return
1825
+ }
1826
+
1827
+ let session = URLSession.shared
1828
+ let task = session.dataTask(with: uRLRequest) { (serviceData, serviceResponse, error) in
1829
+
1830
+ DispatchQueue.main.async {
1831
+ self.hideLoadingIndicator() // Stop loader when response is received
1832
+ }
1833
+
1834
+ if let error = error {
1835
+ print("Error: \(error.localizedDescription)")
1836
+ return
1837
+ }
1838
+
1839
+ guard let httpResponse = serviceResponse as? HTTPURLResponse else {
1840
+ print("Invalid response")
1841
+ return
1842
+ }
1843
+
1844
+ if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
1845
+ if let data = serviceData {
1846
+ do {
1847
+ if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
1848
+ print("Response Data: \(responseObject)")
1849
+
1850
+ // Check if status is 0 and handle the error
1851
+ if let status = responseObject["status"] as? Int, status == 0 {
1852
+ let errorMessage = responseObject["message"] as? String ?? "Unknown error"
1853
+ self.presentPaymentErrorVC(errorMessage: errorMessage)
1854
+ } else {
1855
+ DispatchQueue.main.async {
1856
+ if let paymentDoneVC = self.storyboard?.instantiateViewController(withIdentifier: "ThreeDSecurePaymentDoneVC") as? ThreeDSecurePaymentDoneVC {
1857
+
1858
+ let urlString = responseObject["redirect_url"] as? String ?? responseObject["location_url"] as? String ?? ""
1859
+ paymentDoneVC.redirectURL = urlString
1860
+ paymentDoneVC.chargeData = responseObject
1861
+ paymentDoneVC.easyPayDelegate = self.easyPayDelegate
1862
+ // Pass billing and additional info
1863
+ // Conditionally pass raw FieldItem array
1864
+ paymentDoneVC.visibility = self.visibility
1865
+ paymentDoneVC.amount = self.amount
1866
+ paymentDoneVC.cardApiParams = params
1867
+ paymentDoneVC.request = self.request
1868
+
1869
+ // if self.visibility?.billing == true {
1870
+ paymentDoneVC.billingInfoData = self.billingInfo
1871
+ var billingDict: [String: Any] = [:]
1872
+ self.billingInfo?.forEach { billingDict[$0.name] = $0.value }
1873
+ paymentDoneVC.billingInfo = billingDict
1874
+ // }
1875
+
1876
+ // if self.visibility?.additional == true {
1877
+ paymentDoneVC.additionalInfoData = self.additionalInfo
1878
+ var additionalDict: [String: Any] = [:]
1879
+ self.additionalInfo?.forEach { additionalDict[$0.name] = $0.value }
1880
+ paymentDoneVC.additionalInfo = additionalDict
1881
+ // }
1882
+ self.navigationController?.pushViewController(paymentDoneVC, animated: true)
1883
+ }
1884
+ }
1885
+ }
1886
+ } else {
1887
+ self.presentPaymentErrorVC(errorMessage: "Invalid JSON format")
1888
+ }
1889
+ } catch let jsonError {
1890
+ self.presentPaymentErrorVC(errorMessage: "Error parsing JSON: \(jsonError)")
1891
+ }
1892
+ } else {
1893
+ self.presentPaymentErrorVC(errorMessage: "No data received")
1894
+ }
1895
+ } else {
1896
+ if let data = serviceData,
1897
+ let responseObj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
1898
+ let message = responseObj["message"] as? String {
1899
+ self.presentPaymentErrorVC(errorMessage: message)
1900
+ } else {
1901
+ self.presentPaymentErrorVC(errorMessage: "HTTP Status Code: \(httpResponse.statusCode)")
1902
+ }
1903
+ }
1904
+ }
1905
+ task.resume()
1906
+ }
1907
+
1908
+ @IBAction func actionBtnCancel(_ sender: UIButton) {
1909
+ stopTimer()
1910
+ for viewController in self.navigationController?.viewControllers ?? [] {
1911
+ if viewController is PaymentInfoVC {
1912
+ self.navigationController?.popToViewController(viewController, animated: true)
1913
+ break
1914
+ }
1915
+ }
1916
+ }
1917
+
1918
+ @IBAction func actionBtnResendOTP(_ sender: UIButton) {
1919
+ startTimer()
1920
+ btnResendOTP.isHidden = true
1921
+ imgEsclamationMark.isHidden = false
1922
+ lblOtpTimer.isHidden = false
1923
+ lblUntillResendOtp.isHidden = false
1924
+
1925
+ // Call email verification APi
1926
+ emailVerificationApi()
1927
+ }
1928
+
1929
+ @IBAction func actionBtnConfirmCode(_ sender: UIButton) {
1930
+ otpVerificationApi()
1931
+ }
1932
+
1933
+ }
1934
+
1935
+ extension OTPVerificationVC: UITextFieldDelegate {
1936
+
1937
+ func textFieldDidChangeSelection(_ textField: UITextField) {
1938
+ guard let otpCode = textField.text, otpCode.count >= 6, textField.textContentType == .oneTimeCode else { return }
1939
+ txtFieldOTPText1.text = String(otpCode[otpCode.index(otpCode.startIndex, offsetBy: 0)])
1940
+ txtFieldOTPText2.text = String(otpCode[otpCode.index(otpCode.startIndex, offsetBy: 1)])
1941
+ txtFieldOTPText3.text = String(otpCode[otpCode.index(otpCode.startIndex, offsetBy: 2)])
1942
+ txtFieldOTPText4.text = String(otpCode[otpCode.index(otpCode.startIndex, offsetBy: 3)])
1943
+ txtFieldOTPText5.text = String(otpCode[otpCode.index(otpCode.startIndex, offsetBy: 4)])
1944
+ txtFieldOTPText6.text = String(otpCode[otpCode.index(otpCode.startIndex, offsetBy: 5)])
1945
+ txtFieldOTPText6.becomeFirstResponder()
1946
+ }
1947
+
1948
+ func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
1949
+ if [txtFieldOTPText1, txtFieldOTPText2, txtFieldOTPText3, txtFieldOTPText4, txtFieldOTPText5, txtFieldOTPText6].contains(textField) {
1950
+ if string.count == 1 {
1951
+ // Single character input
1952
+ textField.text = string
1953
+ switch textField {
1954
+ case txtFieldOTPText1:
1955
+ txtFieldOTPText2.becomeFirstResponder()
1956
+ case txtFieldOTPText2:
1957
+ txtFieldOTPText3.becomeFirstResponder()
1958
+ case txtFieldOTPText3:
1959
+ txtFieldOTPText4.becomeFirstResponder()
1960
+ case txtFieldOTPText4:
1961
+ txtFieldOTPText5.becomeFirstResponder()
1962
+ case txtFieldOTPText5:
1963
+ txtFieldOTPText6.becomeFirstResponder()
1964
+ case txtFieldOTPText6:
1965
+ txtFieldOTPText6.resignFirstResponder()
1966
+ // Call OTP verification API when the last field is filled
1967
+ if getCombinedOTP().count == 6 {
1968
+ // otpVerificationApi()
1969
+ }
1970
+ default:
1971
+ break
1972
+ }
1973
+ updateViewColors()
1974
+ return false
1975
+ } else if string.isEmpty {
1976
+ // Handle backspace
1977
+ textField.text = ""
1978
+ switch textField {
1979
+ case txtFieldOTPText6:
1980
+ txtFieldOTPText5.becomeFirstResponder()
1981
+ case txtFieldOTPText5:
1982
+ txtFieldOTPText4.becomeFirstResponder()
1983
+ case txtFieldOTPText4:
1984
+ txtFieldOTPText3.becomeFirstResponder()
1985
+ case txtFieldOTPText3:
1986
+ txtFieldOTPText2.becomeFirstResponder()
1987
+ case txtFieldOTPText2:
1988
+ txtFieldOTPText1.becomeFirstResponder()
1989
+ default:
1990
+ break
1991
+ }
1992
+ updateViewColors()
1993
+ return false
1994
+ }
1995
+ return textField.text?.count == 0
1996
+ }
1997
+
1998
+ return true
1999
+ }
2000
+
2001
+ // Update the view colors based on text presence in OTP fields
2002
+ private func updateViewColors() {
2003
+ if let primaryBtnBackgroundColor = UserStoreSingleton.shared.primary_btn_bg_col,
2004
+ let primaryUIColor = UIColor(hex: primaryBtnBackgroundColor),
2005
+ let secondaryFontColor = UserStoreSingleton.shared.secondary_font_col,
2006
+ let secondaryUIColor = UIColor(hex: secondaryFontColor) {
2007
+
2008
+ viewTextOTP1.layer.borderWidth = 1.0
2009
+ viewTextOTP1.layer.borderColor = txtFieldOTPText1.text?.isEmpty == false ? primaryUIColor.cgColor : secondaryUIColor.cgColor
2010
+ viewTextOTP2.layer.borderWidth = 1.0
2011
+ viewTextOTP2.layer.borderColor = txtFieldOTPText2.text?.isEmpty == false ? primaryUIColor.cgColor : secondaryUIColor.cgColor
2012
+ viewTextOTP3.layer.borderWidth = 1.0
2013
+ viewTextOTP3.layer.borderColor = txtFieldOTPText3.text?.isEmpty == false ? primaryUIColor.cgColor : secondaryUIColor.cgColor
2014
+ viewTextOTP4.layer.borderWidth = 1.0
2015
+ viewTextOTP4.layer.borderColor = txtFieldOTPText4.text?.isEmpty == false ? primaryUIColor.cgColor : secondaryUIColor.cgColor
2016
+ viewTextOTP5.layer.borderWidth = 1.0
2017
+ viewTextOTP5.layer.borderColor = txtFieldOTPText5.text?.isEmpty == false ? primaryUIColor.cgColor : secondaryUIColor.cgColor
2018
+ viewTextOTP6.layer.borderWidth = 1.0
2019
+ viewTextOTP6.layer.borderColor = txtFieldOTPText6.text?.isEmpty == false ? primaryUIColor.cgColor : secondaryUIColor.cgColor
2020
+ }
2021
+ else {
2022
+ viewTextOTP1.layer.borderWidth = 1.0
2023
+ viewTextOTP1.layer.borderColor = (txtFieldOTPText1.text?.isEmpty == false ? UIColor.systemBlue : UIColor.systemGray).cgColor
2024
+ viewTextOTP2.layer.borderWidth = 1.0
2025
+ viewTextOTP2.layer.borderColor = (txtFieldOTPText2.text?.isEmpty == false ? UIColor.systemBlue : UIColor.systemGray).cgColor
2026
+ viewTextOTP3.layer.borderWidth = 1.0
2027
+ viewTextOTP3.layer.borderColor = (txtFieldOTPText3.text?.isEmpty == false ? UIColor.systemBlue : UIColor.systemGray).cgColor
2028
+ viewTextOTP4.layer.borderWidth = 1.0
2029
+ viewTextOTP4.layer.borderColor = (txtFieldOTPText4.text?.isEmpty == false ? UIColor.systemBlue : UIColor.systemGray).cgColor
2030
+ viewTextOTP5.layer.borderWidth = 1.0
2031
+ viewTextOTP5.layer.borderColor = (txtFieldOTPText5.text?.isEmpty == false ? UIColor.systemBlue : UIColor.systemGray).cgColor
2032
+ viewTextOTP6.layer.borderWidth = 1.0
2033
+ viewTextOTP6.layer.borderColor = (txtFieldOTPText6.text?.isEmpty == false ? UIColor.systemBlue : UIColor.systemGray).cgColor
2034
+ }
2035
+ }
2036
+
2037
+ @objc func textFieldDidChange(_ textField: UITextField) {
2038
+ guard let otpCode = textField.text, otpCode.count >= 6, textField.textContentType == .oneTimeCode else { return }
2039
+ // Split the OTP into each text field
2040
+ txtFieldOTPText1.text = String(otpCode[otpCode.index(otpCode.startIndex, offsetBy: 0)])
2041
+ txtFieldOTPText2.text = String(otpCode[otpCode.index(otpCode.startIndex, offsetBy: 1)])
2042
+ txtFieldOTPText3.text = String(otpCode[otpCode.index(otpCode.startIndex, offsetBy: 2)])
2043
+ txtFieldOTPText4.text = String(otpCode[otpCode.index(otpCode.startIndex, offsetBy: 3)])
2044
+ txtFieldOTPText5.text = String(otpCode[otpCode.index(otpCode.startIndex, offsetBy: 4)])
2045
+ txtFieldOTPText6.text = String(otpCode[otpCode.index(otpCode.startIndex, offsetBy: 5)])
2046
+ // Automatically move to the last text field
2047
+ txtFieldOTPText6.becomeFirstResponder()
2048
+ }
2049
+
2050
+ func getCombinedOTP() -> String {
2051
+ let otp1 = txtFieldOTPText1.text ?? ""
2052
+ let otp2 = txtFieldOTPText2.text ?? ""
2053
+ let otp3 = txtFieldOTPText3.text ?? ""
2054
+ let otp4 = txtFieldOTPText4.text ?? ""
2055
+ let otp5 = txtFieldOTPText5.text ?? ""
2056
+ let otp6 = txtFieldOTPText6.text ?? ""
2057
+
2058
+ return otp1 + otp2 + otp3 + otp4 + otp5 + otp6
2059
+ }
2060
+
2061
+ }
2062
+
2063
+
2064
+
2065
+
2066
+