@jimrising/easymerchantsdk-react-native 1.3.5 → 1.3.7

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 (44) hide show
  1. package/README.md +11 -4
  2. package/android/.gradle/8.10/checksums/checksums.lock +0 -0
  3. package/android/.gradle/8.10/dependencies-accessors/gc.properties +0 -0
  4. package/android/.gradle/8.10/fileChanges/last-build.bin +0 -0
  5. package/android/.gradle/8.10/fileHashes/fileHashes.bin +0 -0
  6. package/android/.gradle/8.10/fileHashes/fileHashes.lock +0 -0
  7. package/android/.gradle/8.10/gc.properties +0 -0
  8. package/android/.gradle/8.9/checksums/checksums.lock +0 -0
  9. package/android/.gradle/8.9/dependencies-accessors/gc.properties +0 -0
  10. package/android/.gradle/8.9/fileChanges/last-build.bin +0 -0
  11. package/android/.gradle/8.9/fileHashes/fileHashes.lock +0 -0
  12. package/android/.gradle/8.9/gc.properties +0 -0
  13. package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
  14. package/android/.gradle/buildOutputCleanup/cache.properties +2 -0
  15. package/android/.gradle/nb-cache/trust/0B5D6BE682AD6AEE9815EC13516BF075752CAE5AD5BECDCC00315C37622C2FD3 +1 -0
  16. package/android/.gradle/vcs-1/gc.properties +0 -0
  17. package/ios/Classes/EasyMerchantSdk.m +42 -29
  18. package/ios/Classes/EasyMerchantSdk.swift +68 -9
  19. package/ios/CustomComponents/PlanSelector.swift +28 -30
  20. package/ios/EnvironmentConfig.swift +30 -30
  21. package/ios/Example/ViewController.swift +47 -51
  22. package/ios/Models/AdditionalInfo.swift +43 -6
  23. package/ios/Models/BillingInfo.swift +50 -6
  24. package/ios/Models/RecurringIntervals.swift +34 -0
  25. package/ios/Models/RecurringStartDateType.swift +13 -0
  26. package/ios/Models/Request.swift +120 -11
  27. package/ios/Pods/ViewControllers/AdditionalInfoVC.swift +609 -79
  28. package/ios/Pods/ViewControllers/BillingInfoVC/BillingInfoVC.swift +72 -25
  29. package/ios/Pods/ViewControllers/EmailVerificationVC.swift +14 -0
  30. package/ios/Pods/ViewControllers/OTPVerificationVC.swift +459 -42
  31. package/ios/Pods/ViewControllers/PaymentDoneVC.swift +16 -2
  32. package/ios/Pods/ViewControllers/PaymentErrorVC.swift +0 -2
  33. package/ios/Pods/ViewControllers/PaymentInformation/PaymentInfoVC.swift +1553 -372
  34. package/ios/Pods/ViewControllers/PaymentInformation/SavedAccountsTVC/SavedAccountTVC.swift +6 -2
  35. package/ios/Pods/ViewControllers/PaymentInformation/SavedCardsTVC/SavedCardsTVC.swift +6 -1
  36. package/ios/Pods/ViewControllers/ThreeDSecurePaymentDoneVC.swift +105 -0
  37. package/ios/easymerchantsdk.podspec +1 -1
  38. package/ios/easymerchantsdk.storyboard +554 -57
  39. package/package.json +2 -2
  40. package/.idea/caches/deviceStreaming.xml +0 -571
  41. package/.idea/em-MobileCheckoutSDK-ReactNative.iml +0 -9
  42. package/.idea/misc.xml +0 -5
  43. package/.idea/modules.xml +0 -8
  44. package/.idea/vcs.xml +0 -6
@@ -25,6 +25,11 @@ class AdditionalInfoVC: BaseVC {
25
25
  @IBOutlet weak var lblEasyMerchant: UILabel!
26
26
  @IBOutlet weak var btnCountryCode: UIButton!
27
27
 
28
+ @IBOutlet weak var lblStarNameField: UILabel!
29
+ @IBOutlet weak var lblStarEmailField: UILabel!
30
+ @IBOutlet weak var lblStarPhoneField: UILabel!
31
+ @IBOutlet weak var lblStarDescriptionField: UILabel!
32
+
28
33
  var cardNumber: String?
29
34
  var expiryDate: String?
30
35
  var cvv: String?
@@ -56,9 +61,26 @@ class AdditionalInfoVC: BaseVC {
56
61
 
57
62
  var isSavedNewAccount: Bool?
58
63
 
64
+ var request: Request!
65
+
66
+ var chosenPlan: String?
67
+ var startDate: String?
68
+
59
69
  override func viewDidLoad() {
60
70
  super.viewDidLoad()
61
71
 
72
+ let amountValue = Double(amount ?? 0)
73
+ let amountText = String(format: "$%.2f", amountValue)
74
+ let submitText = request?.submitButtonText ?? ""
75
+
76
+ let defaultTitle = !submitText.isEmpty
77
+ ? "\(submitText) (\(amountText))"
78
+ : "Pay Now (\(amountText))"
79
+
80
+ DispatchQueue.main.async {
81
+ self.btnPayNow.setTitle(defaultTitle, for: .normal)
82
+ }
83
+
62
84
  uiFinishingTouchElements()
63
85
 
64
86
  txtFieldName.delegate = self
@@ -103,15 +125,7 @@ class AdditionalInfoVC: BaseVC {
103
125
  usaCountry.flag = String.flag(for: "US")
104
126
  lblCountryCode.text = "\(usaCountry.flag ?? "") +\(usaCountry.extensionCode ?? "")"
105
127
 
106
- // print(amount ?? "")
107
- // let am = Int(amount ?? "0")
108
- // btnPayNow.setTitle("Pay Now ($\(am ?? 0))", for: .normal)
109
- //
110
- // print("Received customerID: \(customerID ?? "nil")")
111
- // print("Received accountID: \(accountID ?? "nil")")
112
- // print(billingInfoData ?? "")
113
- // print(isFrom)
114
- // print(selectedPaymentMethod ?? "")
128
+ print(cvvText ?? "")
115
129
  }
116
130
 
117
131
  override func viewWillAppear(_ animated: Bool) {
@@ -178,6 +192,23 @@ class AdditionalInfoVC: BaseVC {
178
192
  btnPrevious.titleLabel?.font = UIFont.systemFont(ofSize: fontSize)
179
193
  }
180
194
 
195
+ configureFieldVisibility()
196
+ }
197
+
198
+ private func configureFieldVisibility() {
199
+ if let billingInfoData = billingInfoData,
200
+ let additionalInfo = billingInfoData["additional_info"] as? [String: Any] {
201
+
202
+ let name = additionalInfo["name"] as? String
203
+ let email = additionalInfo["email"] as? String
204
+ let phoneNumber = additionalInfo["phone_number"] as? String
205
+ let description = additionalInfo["description"] as? String
206
+
207
+ lblStarNameField.isHidden = name == nil || name?.isEmpty == true
208
+ lblStarEmailField.isHidden = email == nil || email?.isEmpty == true
209
+ lblStarPhoneField.isHidden = phoneNumber == nil || phoneNumber?.isEmpty == true
210
+ lblStarDescriptionField.isHidden = description == nil || description?.isEmpty == true
211
+ }
181
212
  }
182
213
 
183
214
  @objc func didTapOutside() {
@@ -192,26 +223,39 @@ class AdditionalInfoVC: BaseVC {
192
223
  }
193
224
 
194
225
  @IBAction func actionBtnPrevious(_ sender: UIButton) {
195
- self.navigationController?.popViewController(animated: true)
226
+ navigationController?.popViewController(animated: true)
196
227
  }
197
228
 
198
229
  @IBAction func actionBtnPayNow(_ sender: UIButton) {
199
- if self.txtFieldName.text?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == true {
200
- self.showAlert(title: "Missing Information", message: "Please enter your name.")
230
+ if !lblStarNameField.isHidden &&
231
+ txtFieldName.text?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == true {
232
+ showAlert(title: "Missing Information", message: "Please enter your name.")
233
+ return
201
234
  }
202
- else if self.txtFieldEmail.text?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == true {
203
- self.showAlert(title: "Missing Information", message: "Please enter your email address.")
235
+ else if !lblStarEmailField.isHidden &&
236
+ txtFieldEmail.text?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == true {
237
+ showAlert(title: "Missing Information", message: "Please enter your email address.")
238
+ return
204
239
  }
205
- else if self.txtFieldPhoneNumber.text?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == true {
206
- self.showAlert(title: "Missing Information", message: "Please enter your phone number.")
240
+ else if !lblStarPhoneField.isHidden &&
241
+ txtFieldPhoneNumber.text?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == true {
242
+ showAlert(title: "Missing Information", message: "Please enter your phone number.")
243
+ return
207
244
  }
208
- else if self.txtFieldDescription.text?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == true {
209
- self.showAlert(title: "Missing Information", message: "Please enter description text.")
245
+ else if !lblStarDescriptionField.isHidden &&
246
+ txtFieldDescription.text?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == true {
247
+ showAlert(title: "Missing Information", message: "Please enter description text.")
248
+ return
210
249
  }
211
250
  else {
212
251
  if isSavedNewCard {
213
252
  if isFrom == "AddNewCard" {
214
- self.paymentIntentAddNewCardApi(customerId: UserStoreSingleton.shared.customerId)
253
+ if request.enable3DS == true {
254
+ threeDSecurePaymentAddNewCardApi(customerId: UserStoreSingleton.shared.customerId)
255
+ }
256
+ else {
257
+ self.paymentIntentAddNewCardApi(customerId: UserStoreSingleton.shared.customerId)
258
+ }
215
259
  }
216
260
  }
217
261
  else {
@@ -227,6 +271,9 @@ class AdditionalInfoVC: BaseVC {
227
271
  emailVerificationVC.billingInfoData = billingInfoData
228
272
  emailVerificationVC.selectedPaymentMethod = selectedPaymentMethod
229
273
  emailVerificationVC.easyPayDelegate = self.easyPayDelegate
274
+ emailVerificationVC.request = self.request
275
+ emailVerificationVC.chosenPlan = self.chosenPlan
276
+ emailVerificationVC.startDate = self.startDate
230
277
  self.navigationController?.pushViewController(emailVerificationVC, animated: true)
231
278
  }
232
279
  } else if selectedPaymentMethod == "Bank" {
@@ -239,6 +286,9 @@ class AdditionalInfoVC: BaseVC {
239
286
  emailVerificationVC.billingInfoData = billingInfoData
240
287
  emailVerificationVC.selectedPaymentMethod = selectedPaymentMethod
241
288
  emailVerificationVC.easyPayDelegate = self.easyPayDelegate
289
+ emailVerificationVC.request = self.request
290
+ emailVerificationVC.chosenPlan = self.chosenPlan
291
+ emailVerificationVC.startDate = self.startDate
242
292
  self.navigationController?.pushViewController(emailVerificationVC, animated: true)
243
293
  }
244
294
  }
@@ -249,7 +299,12 @@ class AdditionalInfoVC: BaseVC {
249
299
  paymentIntentFromShowCardApi()
250
300
  }
251
301
  else {
252
- paymentIntentApi()
302
+ if request.enable3DS == true {
303
+ threeDSecurePaymentApi()
304
+ }
305
+ else {
306
+ paymentIntentApi()
307
+ }
253
308
  }
254
309
  }
255
310
  else if selectedPaymentMethod == "Bank" {
@@ -293,13 +348,13 @@ class AdditionalInfoVC: BaseVC {
293
348
  return
294
349
  }
295
350
 
296
- var request = URLRequest(url: serviceURL)
297
- request.httpMethod = "POST"
298
- request.addValue("application/json", forHTTPHeaderField: "Content-Type")
351
+ var urlRequest = URLRequest(url: serviceURL)
352
+ urlRequest.httpMethod = "POST"
353
+ urlRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
299
354
 
300
355
  let token = UserStoreSingleton.shared.clientToken
301
356
  print("Setting clientToken header: \(token ?? "None")")
302
- request.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
357
+ urlRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
303
358
 
304
359
  guard let billingInfoData = billingInfoData else {
305
360
  print("Billing info data is nil")
@@ -325,7 +380,7 @@ class AdditionalInfoVC: BaseVC {
325
380
  "postal_code": billingInfoData["postal_code"] as? String ?? ""
326
381
  ]
327
382
 
328
- let params: [String: Any] = [
383
+ var params: [String: Any] = [
329
384
  "name": txtFieldName.text ?? "",
330
385
  "email": txtFieldEmail.text ?? "",
331
386
  "card_number": cardNumber?.replacingOccurrences(of: " ", with: "") ?? "",
@@ -341,9 +396,32 @@ class AdditionalInfoVC: BaseVC {
341
396
  "save_card": 0
342
397
  ]
343
398
 
399
+ // Add these if recurring is enabled
400
+ if let req = request, req.is_recurring == true {
401
+ if let recurringType = req.recurringStartDateType, recurringType == .custom {
402
+ // Only send start_date if type is .custom and field is not empty
403
+ if let startDateText = startDate, !startDateText.isEmpty {
404
+ let inputFormatter = DateFormatter()
405
+ inputFormatter.dateFormat = "dd/MM/yyyy"
406
+
407
+ let outputFormatter = DateFormatter()
408
+ outputFormatter.dateFormat = "MM/dd/yyyy"
409
+
410
+ if let date = inputFormatter.date(from: startDateText) {
411
+ let apiFormattedDate = outputFormatter.string(from: date)
412
+ params["start_date"] = apiFormattedDate
413
+ } else {
414
+ print("Invalid date format in startDateText")
415
+ }
416
+ }
417
+ }
418
+
419
+ params["interval"] = chosenPlan?.lowercased()
420
+ }
421
+
344
422
  do {
345
423
  let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
346
- request.httpBody = jsonData
424
+ urlRequest.httpBody = jsonData
347
425
  if let jsonString = String(data: jsonData, encoding: .utf8) {
348
426
  print("JSON Payload: \(jsonString)")
349
427
  }
@@ -354,7 +432,7 @@ class AdditionalInfoVC: BaseVC {
354
432
  }
355
433
 
356
434
  let session = URLSession.shared
357
- let task = session.dataTask(with: request) { (serviceData, serviceResponse, error) in
435
+ let task = session.dataTask(with: urlRequest) { (serviceData, serviceResponse, error) in
358
436
 
359
437
  DispatchQueue.main.async {
360
438
  self.hideLoadingIndicator()
@@ -412,7 +490,7 @@ class AdditionalInfoVC: BaseVC {
412
490
  }
413
491
  task.resume()
414
492
  }
415
-
493
+
416
494
  //MARK: - Credit Card Charge Api from Saved cards
417
495
  func paymentIntentFromShowCardApi() {
418
496
  showLoadingIndicator()
@@ -425,24 +503,24 @@ class AdditionalInfoVC: BaseVC {
425
503
  return
426
504
  }
427
505
 
428
- var request = URLRequest(url: serviceURL)
429
- request.httpMethod = "POST"
430
- request.addValue("application/json", forHTTPHeaderField: "Content-Type")
506
+ var uRLRequest = URLRequest(url: serviceURL)
507
+ uRLRequest.httpMethod = "POST"
508
+ uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
431
509
 
432
510
  let token = UserStoreSingleton.shared.clientToken
433
511
  print("Setting clientToken header: \(token ?? "None")")
434
- request.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
512
+ uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
435
513
 
436
514
  guard let billingInfoData = billingInfoData else {
437
515
  print("Billing info data is nil")
438
516
  return
439
517
  }
440
518
 
441
- // // Remove the flag and "+" sign from lblCountryCode.text
442
- // let countryCode = lblCountryCode.text ?? ""
443
- // let cleanedCountryCode = countryCode.replacingOccurrences(of: "[^0-9]", with: "", options: .regularExpression)
444
- // // Format phone number parameter by combining cleaned country code and phone number
445
- // let phoneNumber = "\(cleanedCountryCode)\(txtFieldPhoneNumber.text ?? "")"
519
+ // // Remove the flag and "+" sign from lblCountryCode.text
520
+ // let countryCode = lblCountryCode.text ?? ""
521
+ // let cleanedCountryCode = countryCode.replacingOccurrences(of: "[^0-9]", with: "", options: .regularExpression)
522
+ // // Format phone number parameter by combining cleaned country code and phone number
523
+ // let phoneNumber = "\(cleanedCountryCode)\(txtFieldPhoneNumber.text ?? "")"
446
524
 
447
525
  // Extract only the digits from the phone number (local only, no country code)
448
526
  let localPhone = txtFieldPhoneNumber.text?.components(separatedBy: CharacterSet.decimalDigits.inverted).joined() ?? ""
@@ -451,7 +529,7 @@ class AdditionalInfoVC: BaseVC {
451
529
  "name": txtFieldName.text ?? "",
452
530
  "email": txtFieldEmail.text ?? "",
453
531
  "phone_number": localPhone,
454
- // "phone_number": phoneNumber,
532
+ // "phone_number": phoneNumber,
455
533
  "description": txtFieldDescription.text ?? ""
456
534
  ]
457
535
 
@@ -463,7 +541,7 @@ class AdditionalInfoVC: BaseVC {
463
541
  "postal_code": billingInfoData["postal_code"] as? String ?? ""
464
542
  ]
465
543
 
466
- let params: [String: Any] = [
544
+ var params: [String: Any] = [
467
545
  "name": txtFieldName.text ?? "",
468
546
  "email": txtFieldEmail.text ?? "",
469
547
  "description": txtFieldDescription.text ?? "",
@@ -478,9 +556,32 @@ class AdditionalInfoVC: BaseVC {
478
556
  "cvc" : cvvText ?? ""
479
557
  ]
480
558
 
559
+ // Add these if recurring is enabled
560
+ if let req = request, req.is_recurring == true {
561
+ if let recurringType = req.recurringStartDateType, recurringType == .custom {
562
+ // Only send start_date if type is .custom and field is not empty
563
+ if let startDateText = startDate, !startDateText.isEmpty {
564
+ let inputFormatter = DateFormatter()
565
+ inputFormatter.dateFormat = "dd/MM/yyyy"
566
+
567
+ let outputFormatter = DateFormatter()
568
+ outputFormatter.dateFormat = "MM/dd/yyyy"
569
+
570
+ if let date = inputFormatter.date(from: startDateText) {
571
+ let apiFormattedDate = outputFormatter.string(from: date)
572
+ params["start_date"] = apiFormattedDate
573
+ } else {
574
+ print("Invalid date format in startDateText")
575
+ }
576
+ }
577
+ }
578
+
579
+ params["interval"] = chosenPlan?.lowercased()
580
+ }
581
+
481
582
  do {
482
583
  let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
483
- request.httpBody = jsonData
584
+ uRLRequest.httpBody = jsonData
484
585
  if let jsonString = String(data: jsonData, encoding: .utf8) {
485
586
  print("JSON Payload: \(jsonString)")
486
587
  }
@@ -491,7 +592,7 @@ class AdditionalInfoVC: BaseVC {
491
592
  }
492
593
 
493
594
  let session = URLSession.shared
494
- let task = session.dataTask(with: request) { (serviceData, serviceResponse, error) in
595
+ let task = session.dataTask(with: uRLRequest) { (serviceData, serviceResponse, error) in
495
596
 
496
597
  DispatchQueue.main.async {
497
598
  self.hideLoadingIndicator() // Stop loader when response is received
@@ -561,13 +662,13 @@ class AdditionalInfoVC: BaseVC {
561
662
  return
562
663
  }
563
664
 
564
- var request = URLRequest(url: serviceURL)
565
- request.httpMethod = "POST"
566
- request.addValue("application/json", forHTTPHeaderField: "Content-Type")
665
+ var uRLRequest = URLRequest(url: serviceURL)
666
+ uRLRequest.httpMethod = "POST"
667
+ uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
567
668
 
568
669
  let token = UserStoreSingleton.shared.clientToken
569
670
  print("Setting clientToken header: \(token ?? "None")")
570
- request.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
671
+ uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
571
672
 
572
673
  guard let billingInfoData = billingInfoData else {
573
674
  print("Billing info data is nil")
@@ -613,6 +714,29 @@ class AdditionalInfoVC: BaseVC {
613
714
  params["is_default"] = "1"
614
715
  }
615
716
 
717
+ // Add these if recurring is enabled
718
+ if let req = request, req.is_recurring == true {
719
+ if let recurringType = req.recurringStartDateType, recurringType == .custom {
720
+ // Only send start_date if type is .custom and field is not empty
721
+ if let startDateText = startDate, !startDateText.isEmpty {
722
+ let inputFormatter = DateFormatter()
723
+ inputFormatter.dateFormat = "dd/MM/yyyy"
724
+
725
+ let outputFormatter = DateFormatter()
726
+ outputFormatter.dateFormat = "MM/dd/yyyy"
727
+
728
+ if let date = inputFormatter.date(from: startDateText) {
729
+ let apiFormattedDate = outputFormatter.string(from: date)
730
+ params["start_date"] = apiFormattedDate
731
+ } else {
732
+ print("Invalid date format in startDateText")
733
+ }
734
+ }
735
+ }
736
+
737
+ params["interval"] = chosenPlan?.lowercased()
738
+ }
739
+
616
740
  if let customerId = customerId {
617
741
  params["customer"] = customerId
618
742
  params["customer_id"] = customerId
@@ -625,7 +749,7 @@ class AdditionalInfoVC: BaseVC {
625
749
 
626
750
  do {
627
751
  let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
628
- request.httpBody = jsonData
752
+ uRLRequest.httpBody = jsonData
629
753
  if let jsonString = String(data: jsonData, encoding: .utf8) {
630
754
  print("JSON Payload: \(jsonString)")
631
755
  }
@@ -636,7 +760,7 @@ class AdditionalInfoVC: BaseVC {
636
760
  }
637
761
 
638
762
  let session = URLSession.shared
639
- let task = session.dataTask(with: request) { (serviceData, serviceResponse, error) in
763
+ let task = session.dataTask(with: uRLRequest) { (serviceData, serviceResponse, error) in
640
764
 
641
765
  DispatchQueue.main.async {
642
766
  self.hideLoadingIndicator() // Stop loader when response is received
@@ -706,24 +830,24 @@ class AdditionalInfoVC: BaseVC {
706
830
  return
707
831
  }
708
832
 
709
- var request = URLRequest(url: serviceURL)
710
- request.httpMethod = "POST"
711
- request.addValue("application/json", forHTTPHeaderField: "Content-Type")
833
+ var uRLRequest = URLRequest(url: serviceURL)
834
+ uRLRequest.httpMethod = "POST"
835
+ uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
712
836
 
713
837
  let token = UserStoreSingleton.shared.clientToken
714
838
  print("Setting clientToken header: \(token ?? "None")")
715
- request.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
839
+ uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
716
840
 
717
841
  guard let billingInfoData = billingInfoData else {
718
842
  print("Billing info data is nil")
719
843
  return
720
844
  }
721
845
 
722
- // // Remove the flag and "+" sign from lblCountryCode.text
723
- // let countryCode = lblCountryCode.text ?? ""
724
- // let cleanedCountryCode = countryCode.replacingOccurrences(of: "[^0-9]", with: "", options: .regularExpression)
725
- // // Format phone number parameter by combining cleaned country code and phone number
726
- // let phoneNumber = "\(cleanedCountryCode)\(txtFieldPhoneNumber.text ?? "")"
846
+ // // Remove the flag and "+" sign from lblCountryCode.text
847
+ // let countryCode = lblCountryCode.text ?? ""
848
+ // let cleanedCountryCode = countryCode.replacingOccurrences(of: "[^0-9]", with: "", options: .regularExpression)
849
+ // // Format phone number parameter by combining cleaned country code and phone number
850
+ // let phoneNumber = "\(cleanedCountryCode)\(txtFieldPhoneNumber.text ?? "")"
727
851
 
728
852
  // Extract only the digits from the phone number (local only, no country code)
729
853
  let localPhone = txtFieldPhoneNumber.text?.components(separatedBy: CharacterSet.decimalDigits.inverted).joined() ?? ""
@@ -732,7 +856,7 @@ class AdditionalInfoVC: BaseVC {
732
856
  "name": txtFieldName.text ?? "",
733
857
  "email": txtFieldEmail.text ?? "",
734
858
  "phone_number": localPhone,
735
- // "phone_number": phoneNumber,
859
+ // "phone_number": phoneNumber,
736
860
  "description": txtFieldDescription.text ?? ""
737
861
  ]
738
862
 
@@ -744,7 +868,7 @@ class AdditionalInfoVC: BaseVC {
744
868
  "postal_code": billingInfoData["postal_code"] as? String ?? ""
745
869
  ]
746
870
 
747
- let params: [String: Any] = [
871
+ var params: [String: Any] = [
748
872
  "name": txtFieldName.text ?? "",
749
873
  "email": txtFieldEmail.text ?? "",
750
874
  "description": txtFieldDescription.text ?? "",
@@ -759,11 +883,34 @@ class AdditionalInfoVC: BaseVC {
759
883
  "levelIndicator": 1,
760
884
  ]
761
885
 
886
+ // Add these if recurring is enabled
887
+ if let req = request, req.is_recurring == true {
888
+ if let recurringType = req.recurringStartDateType, recurringType == .custom {
889
+ // Only send start_date if type is .custom and field is not empty
890
+ if let startDateText = startDate, !startDateText.isEmpty {
891
+ let inputFormatter = DateFormatter()
892
+ inputFormatter.dateFormat = "dd/MM/yyyy"
893
+
894
+ let outputFormatter = DateFormatter()
895
+ outputFormatter.dateFormat = "MM/dd/yyyy"
896
+
897
+ if let date = inputFormatter.date(from: startDateText) {
898
+ let apiFormattedDate = outputFormatter.string(from: date)
899
+ params["start_date"] = apiFormattedDate
900
+ } else {
901
+ print("Invalid date format in startDateText")
902
+ }
903
+ }
904
+ }
905
+
906
+ params["interval"] = chosenPlan?.lowercased()
907
+ }
908
+
762
909
  print(params)
763
910
 
764
911
  do {
765
912
  let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
766
- request.httpBody = jsonData
913
+ uRLRequest.httpBody = jsonData
767
914
  if let jsonString = String(data: jsonData, encoding: .utf8) {
768
915
  print("JSON Payload: \(jsonString)")
769
916
  }
@@ -774,7 +921,7 @@ class AdditionalInfoVC: BaseVC {
774
921
  }
775
922
 
776
923
  let session = URLSession.shared
777
- let task = session.dataTask(with: request) { (serviceData, serviceResponse, error) in
924
+ let task = session.dataTask(with: uRLRequest) { (serviceData, serviceResponse, error) in
778
925
 
779
926
  DispatchQueue.main.async {
780
927
  self.hideLoadingIndicator() // Stop loader when response is received
@@ -845,24 +992,24 @@ class AdditionalInfoVC: BaseVC {
845
992
  return
846
993
  }
847
994
 
848
- var request = URLRequest(url: serviceURL)
849
- request.httpMethod = "POST"
850
- request.addValue("application/json", forHTTPHeaderField: "Content-Type")
995
+ var uRLRequest = URLRequest(url: serviceURL)
996
+ uRLRequest.httpMethod = "POST"
997
+ uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
851
998
 
852
999
  let token = UserStoreSingleton.shared.clientToken
853
1000
  print("Setting clientToken header: \(token ?? "None")")
854
- request.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
1001
+ uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
855
1002
 
856
1003
  guard let billingInfoData = billingInfoData else {
857
1004
  print("Billing info data is nil")
858
1005
  return
859
1006
  }
860
1007
 
861
- // // Remove the flag and "+" sign from lblCountryCode.text
862
- // let countryCode = lblCountryCode.text ?? ""
863
- // let cleanedCountryCode = countryCode.replacingOccurrences(of: "[^0-9]", with: "", options: .regularExpression)
864
- // // Format phone number parameter by combining cleaned country code and phone number
865
- // let phoneNumber = "\(cleanedCountryCode)\(txtFieldPhoneNumber.text ?? "")"
1008
+ // // Remove the flag and "+" sign from lblCountryCode.text
1009
+ // let countryCode = lblCountryCode.text ?? ""
1010
+ // let cleanedCountryCode = countryCode.replacingOccurrences(of: "[^0-9]", with: "", options: .regularExpression)
1011
+ // // Format phone number parameter by combining cleaned country code and phone number
1012
+ // let phoneNumber = "\(cleanedCountryCode)\(txtFieldPhoneNumber.text ?? "")"
866
1013
 
867
1014
  // Extract only the digits from the phone number (local only, no country code)
868
1015
  let localPhone = txtFieldPhoneNumber.text?.components(separatedBy: CharacterSet.decimalDigits.inverted).joined() ?? ""
@@ -871,7 +1018,7 @@ class AdditionalInfoVC: BaseVC {
871
1018
  "name": txtFieldName.text ?? "",
872
1019
  "email": txtFieldEmail.text ?? "",
873
1020
  "phone_number": localPhone,
874
- // "phone_number": phoneNumber,
1021
+ // "phone_number": phoneNumber,
875
1022
  "description": txtFieldDescription.text ?? ""
876
1023
  ]
877
1024
 
@@ -883,7 +1030,7 @@ class AdditionalInfoVC: BaseVC {
883
1030
  "postal_code": billingInfoData["postal_code"] as? String ?? ""
884
1031
  ]
885
1032
 
886
- let params: [String: Any] = [
1033
+ var params: [String: Any] = [
887
1034
  "name": UserStoreSingleton.shared.merchantName ?? "",
888
1035
  "account_id": accountID ?? "",
889
1036
  "payment_method": "ach",
@@ -895,11 +1042,34 @@ class AdditionalInfoVC: BaseVC {
895
1042
  "additional_info": additionalInfo
896
1043
  ]
897
1044
 
1045
+ // Add these if recurring is enabled
1046
+ if let req = request, req.is_recurring == true {
1047
+ if let recurringType = req.recurringStartDateType, recurringType == .custom {
1048
+ // Only send start_date if type is .custom and field is not empty
1049
+ if let startDateText = startDate, !startDateText.isEmpty {
1050
+ let inputFormatter = DateFormatter()
1051
+ inputFormatter.dateFormat = "dd/MM/yyyy"
1052
+
1053
+ let outputFormatter = DateFormatter()
1054
+ outputFormatter.dateFormat = "MM/dd/yyyy"
1055
+
1056
+ if let date = inputFormatter.date(from: startDateText) {
1057
+ let apiFormattedDate = outputFormatter.string(from: date)
1058
+ params["start_date"] = apiFormattedDate
1059
+ } else {
1060
+ print("Invalid date format in startDateText")
1061
+ }
1062
+ }
1063
+ }
1064
+
1065
+ params["interval"] = chosenPlan?.lowercased()
1066
+ }
1067
+
898
1068
  print(params)
899
1069
 
900
1070
  do {
901
1071
  let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
902
- request.httpBody = jsonData
1072
+ uRLRequest.httpBody = jsonData
903
1073
  if let jsonString = String(data: jsonData, encoding: .utf8) {
904
1074
  print("JSON Payload: \(jsonString)")
905
1075
  }
@@ -910,7 +1080,7 @@ class AdditionalInfoVC: BaseVC {
910
1080
  }
911
1081
 
912
1082
  let session = URLSession.shared
913
- let task = session.dataTask(with: request) { (serviceData, serviceResponse, error) in
1083
+ let task = session.dataTask(with: uRLRequest) { (serviceData, serviceResponse, error) in
914
1084
 
915
1085
  DispatchQueue.main.async {
916
1086
  self.hideLoadingIndicator() // Stop loader when response is received
@@ -981,13 +1151,13 @@ class AdditionalInfoVC: BaseVC {
981
1151
  return
982
1152
  }
983
1153
 
984
- var request = URLRequest(url: serviceURL)
985
- request.httpMethod = "POST"
986
- request.addValue("application/json", forHTTPHeaderField: "Content-Type")
1154
+ var uRLRequest = URLRequest(url: serviceURL)
1155
+ uRLRequest.httpMethod = "POST"
1156
+ uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
987
1157
 
988
1158
  let token = UserStoreSingleton.shared.clientToken
989
1159
  print("Setting clientToken header: \(token ?? "None")")
990
- request.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
1160
+ uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
991
1161
 
992
1162
  guard let billingInfoData = billingInfoData else {
993
1163
  print("Billing info data is nil")
@@ -1036,11 +1206,34 @@ class AdditionalInfoVC: BaseVC {
1036
1206
  params["username"] = emailPrefix
1037
1207
  }
1038
1208
 
1209
+ // Add these if recurring is enabled
1210
+ if let req = request, req.is_recurring == true {
1211
+ if let recurringType = req.recurringStartDateType, recurringType == .custom {
1212
+ // Only send start_date if type is .custom and field is not empty
1213
+ if let startDateText = startDate, !startDateText.isEmpty {
1214
+ let inputFormatter = DateFormatter()
1215
+ inputFormatter.dateFormat = "dd/MM/yyyy"
1216
+
1217
+ let outputFormatter = DateFormatter()
1218
+ outputFormatter.dateFormat = "MM/dd/yyyy"
1219
+
1220
+ if let date = inputFormatter.date(from: startDateText) {
1221
+ let apiFormattedDate = outputFormatter.string(from: date)
1222
+ params["start_date"] = apiFormattedDate
1223
+ } else {
1224
+ print("Invalid date format in startDateText")
1225
+ }
1226
+ }
1227
+ }
1228
+
1229
+ params["interval"] = chosenPlan?.lowercased()
1230
+ }
1231
+
1039
1232
  print(params)
1040
1233
 
1041
1234
  do {
1042
1235
  let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
1043
- request.httpBody = jsonData
1236
+ uRLRequest.httpBody = jsonData
1044
1237
  if let jsonString = String(data: jsonData, encoding: .utf8) {
1045
1238
  print("JSON Payload: \(jsonString)")
1046
1239
  }
@@ -1051,7 +1244,7 @@ class AdditionalInfoVC: BaseVC {
1051
1244
  }
1052
1245
 
1053
1246
  let session = URLSession.shared
1054
- let task = session.dataTask(with: request) { (serviceData, serviceResponse, error) in
1247
+ let task = session.dataTask(with: uRLRequest) { (serviceData, serviceResponse, error) in
1055
1248
 
1056
1249
  DispatchQueue.main.async {
1057
1250
  self.hideLoadingIndicator() // Stop loader when response is received
@@ -1109,6 +1302,343 @@ class AdditionalInfoVC: BaseVC {
1109
1302
  task.resume()
1110
1303
  }
1111
1304
 
1305
+ // MARK: - 3DS Functionality
1306
+
1307
+ // MARK: - Credit Card Charge Api If Billing info is not nil and Without Login.
1308
+ func threeDSecurePaymentApi() {
1309
+ showLoadingIndicator()
1310
+
1311
+ let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.threeDSecure.path()
1312
+
1313
+ guard let serviceURL = URL(string: fullURL) else {
1314
+ print("Invalid URL")
1315
+ hideLoadingIndicator()
1316
+ return
1317
+ }
1318
+
1319
+ var uRLRequest = URLRequest(url: serviceURL)
1320
+ uRLRequest.httpMethod = "POST"
1321
+ uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
1322
+
1323
+ let token = UserStoreSingleton.shared.clientToken
1324
+ print("Setting clientToken header: \(token ?? "None")")
1325
+ uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
1326
+
1327
+ // Add API headers
1328
+ if let apiKey = EnvironmentConfig.apiKey,
1329
+ let apiSecret = EnvironmentConfig.apiSecret {
1330
+ uRLRequest.addValue(apiKey, forHTTPHeaderField: "x-api-key")
1331
+ uRLRequest.addValue(apiSecret, forHTTPHeaderField: "x-api-secret")
1332
+ }
1333
+
1334
+ guard let billingInfoData = billingInfoData else {
1335
+ print("Billing info data is nil")
1336
+ hideLoadingIndicator()
1337
+ return
1338
+ }
1339
+
1340
+ // Extract only the digits from the phone number (local only, no country code)
1341
+ let localPhone = txtFieldPhoneNumber.text?.components(separatedBy: CharacterSet.decimalDigits.inverted).joined() ?? ""
1342
+
1343
+ let additionalInfo: [String: Any] = [
1344
+ "name": txtFieldName.text ?? "",
1345
+ "email": txtFieldEmail.text ?? "",
1346
+ "phone_number": localPhone,
1347
+ "description": txtFieldDescription.text ?? ""
1348
+ ]
1349
+
1350
+ let billingInfo: [String: Any] = [
1351
+ "address": billingInfoData["address"] as? String ?? "",
1352
+ "country": billingInfoData["country"] as? String ?? "",
1353
+ "state": billingInfoData["state"] as? String ?? "",
1354
+ "city": billingInfoData["city"] as? String ?? "",
1355
+ "postal_code": billingInfoData["postal_code"] as? String ?? ""
1356
+ ]
1357
+
1358
+ var params: [String: Any] = [
1359
+ "name": nameOnCard ?? "",
1360
+ "email": txtFieldEmail.text ?? "",
1361
+ "card_number": cardNumber?.replacingOccurrences(of: " ", with: "") ?? "",
1362
+ "cardholder_name": nameOnCard ?? "",
1363
+ "exp_month": expiryDate?.components(separatedBy: "/").first ?? "",
1364
+ "exp_year": expiryDate?.components(separatedBy: "/").last ?? "",
1365
+ "cvc": cvv ?? "",
1366
+ "description": "Test",
1367
+ "currency": "usd",
1368
+ "tokenize": request.tokenOnly ?? false,
1369
+ "address": billingInfoData["address"] as? String ?? "",
1370
+ "billing_info": billingInfo,
1371
+ "additional_info": additionalInfo
1372
+ ]
1373
+
1374
+ // Add these if recurring is enabled
1375
+ if let req = request, req.is_recurring == true {
1376
+ if let recurringType = req.recurringStartDateType, recurringType == .custom {
1377
+ // Only send start_date if type is .custom and field is not empty
1378
+ if let startDateText = startDate, !startDateText.isEmpty {
1379
+ let inputFormatter = DateFormatter()
1380
+ inputFormatter.dateFormat = "dd/MM/yyyy"
1381
+
1382
+ let outputFormatter = DateFormatter()
1383
+ outputFormatter.dateFormat = "MM/dd/yyyy"
1384
+
1385
+ if let date = inputFormatter.date(from: startDateText) {
1386
+ let apiFormattedDate = outputFormatter.string(from: date)
1387
+ params["start_date"] = apiFormattedDate
1388
+ } else {
1389
+ print("Invalid date format in startDateText")
1390
+ }
1391
+ }
1392
+ }
1393
+
1394
+ params["interval"] = chosenPlan?.lowercased()
1395
+ }
1396
+
1397
+ do {
1398
+ let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
1399
+ uRLRequest.httpBody = jsonData
1400
+ if let jsonString = String(data: jsonData, encoding: .utf8) {
1401
+ print("JSON Payload: \(jsonString)")
1402
+ }
1403
+ } catch let error {
1404
+ print("Error creating JSON data: \(error)")
1405
+ hideLoadingIndicator()
1406
+ return
1407
+ }
1408
+
1409
+ let session = URLSession.shared
1410
+ let task = session.dataTask(with: uRLRequest) { (serviceData, serviceResponse, error) in
1411
+
1412
+ DispatchQueue.main.async {
1413
+ self.hideLoadingIndicator() // Stop loader when response is received
1414
+ }
1415
+
1416
+ if let error = error {
1417
+ print("Error: \(error.localizedDescription)")
1418
+ return
1419
+ }
1420
+
1421
+ guard let httpResponse = serviceResponse as? HTTPURLResponse else {
1422
+ print("Invalid response")
1423
+ return
1424
+ }
1425
+
1426
+ if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
1427
+ if let data = serviceData {
1428
+ do {
1429
+ if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
1430
+ print("Response Data: \(responseObject)")
1431
+
1432
+ // Check if status is 0 and handle the error
1433
+ if let status = responseObject["status"] as? Int, status == 0 {
1434
+ let errorMessage = responseObject["message"] as? String ?? "Unknown error"
1435
+ self.presentPaymentErrorVC(errorMessage: errorMessage)
1436
+ } else {
1437
+ DispatchQueue.main.async {
1438
+ if let paymentDoneVC = self.storyboard?.instantiateViewController(withIdentifier: "ThreeDSecurePaymentDoneVC") as? ThreeDSecurePaymentDoneVC {
1439
+
1440
+ let urlString = responseObject["redirect_url"] as? String ?? responseObject["location_url"] as? String ?? ""
1441
+ paymentDoneVC.redirectURL = urlString
1442
+ paymentDoneVC.chargeData = responseObject
1443
+ paymentDoneVC.easyPayDelegate = self.easyPayDelegate
1444
+
1445
+ self.navigationController?.pushViewController(paymentDoneVC, animated: true)
1446
+ }
1447
+ }
1448
+ }
1449
+ } else {
1450
+ self.presentPaymentErrorVC(errorMessage: "Invalid JSON format")
1451
+ }
1452
+ } catch let jsonError {
1453
+ self.presentPaymentErrorVC(errorMessage: "Error parsing JSON: \(jsonError)")
1454
+ }
1455
+ } else {
1456
+ self.presentPaymentErrorVC(errorMessage: "No data received")
1457
+ }
1458
+ } else {
1459
+ if let data = serviceData,
1460
+ let responseObj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
1461
+ let message = responseObj["message"] as? String {
1462
+ self.presentPaymentErrorVC(errorMessage: message)
1463
+ } else {
1464
+ self.presentPaymentErrorVC(errorMessage: "HTTP Status Code: \(httpResponse.statusCode)")
1465
+ }
1466
+ }
1467
+ }
1468
+ task.resume()
1469
+ }
1470
+
1471
+ // MARK: - Credit Card Charge Api If Billing info is not nil With Login from Add New Card.
1472
+ func threeDSecurePaymentAddNewCardApi(customerId: String?) {
1473
+ showLoadingIndicator()
1474
+
1475
+ let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.threeDSecure.path()
1476
+
1477
+ guard let serviceURL = URL(string: fullURL) else {
1478
+ print("Invalid URL")
1479
+ hideLoadingIndicator()
1480
+ return
1481
+ }
1482
+
1483
+ var uRLRequest = URLRequest(url: serviceURL)
1484
+ uRLRequest.httpMethod = "POST"
1485
+ uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
1486
+
1487
+ let token = UserStoreSingleton.shared.clientToken
1488
+ print("Setting clientToken header: \(token ?? "None")")
1489
+ uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
1490
+
1491
+ // Add API headers
1492
+ if let apiKey = EnvironmentConfig.apiKey,
1493
+ let apiSecret = EnvironmentConfig.apiSecret {
1494
+ uRLRequest.addValue(apiKey, forHTTPHeaderField: "x-api-key")
1495
+ uRLRequest.addValue(apiSecret, forHTTPHeaderField: "x-api-secret")
1496
+ }
1497
+
1498
+ guard let billingInfoData = billingInfoData else {
1499
+ print("Billing info data is nil")
1500
+ hideLoadingIndicator()
1501
+ return
1502
+ }
1503
+
1504
+ // Extract only the digits from the phone number (local only, no country code)
1505
+ let localPhone = txtFieldPhoneNumber.text?.components(separatedBy: CharacterSet.decimalDigits.inverted).joined() ?? ""
1506
+
1507
+ let additionalInfo: [String: Any] = [
1508
+ "name": txtFieldName.text ?? "",
1509
+ "email": txtFieldEmail.text ?? "",
1510
+ "phone_number": localPhone,
1511
+ "description": txtFieldDescription.text ?? ""
1512
+ ]
1513
+
1514
+ let billingInfo: [String: Any] = [
1515
+ "address": billingInfoData["address"] as? String ?? "",
1516
+ "country": billingInfoData["country"] as? String ?? "",
1517
+ "state": billingInfoData["state"] as? String ?? "",
1518
+ "city": billingInfoData["city"] as? String ?? "",
1519
+ "postal_code": billingInfoData["postal_code"] as? String ?? ""
1520
+ ]
1521
+
1522
+ var params: [String: Any] = [
1523
+ "name": nameOnCard ?? "",
1524
+ "email": txtFieldEmail.text ?? "",
1525
+ "card_number": cardNumber?.replacingOccurrences(of: " ", with: "") ?? "",
1526
+ "cardholder_name": nameOnCard ?? "",
1527
+ "exp_month": expiryDate?.components(separatedBy: "/").first ?? "",
1528
+ "exp_year": expiryDate?.components(separatedBy: "/").last ?? "",
1529
+ "cvc": cvv ?? "",
1530
+ "description": "Test",
1531
+ "currency": "usd",
1532
+ "tokenize": request.tokenOnly ?? false,
1533
+ "address": billingInfoData["address"] as? String ?? "",
1534
+ "billing_info": billingInfo,
1535
+ "additional_info": additionalInfo,
1536
+ "save_card": isSavedNewCard ? 1 : 0,
1537
+ "customer_id": customerId ?? ""
1538
+ ]
1539
+
1540
+ // Add is_default parameter if save_card is 1
1541
+ if isSavedNewCard {
1542
+ params["is_default"] = "1"
1543
+ }
1544
+
1545
+ // Add these if recurring is enabled
1546
+ if let req = request, req.is_recurring == true {
1547
+ if let recurringType = req.recurringStartDateType, recurringType == .custom {
1548
+ // Only send start_date if type is .custom and field is not empty
1549
+ if let startDateText = startDate, !startDateText.isEmpty {
1550
+ let inputFormatter = DateFormatter()
1551
+ inputFormatter.dateFormat = "dd/MM/yyyy"
1552
+
1553
+ let outputFormatter = DateFormatter()
1554
+ outputFormatter.dateFormat = "MM/dd/yyyy"
1555
+
1556
+ if let date = inputFormatter.date(from: startDateText) {
1557
+ let apiFormattedDate = outputFormatter.string(from: date)
1558
+ params["start_date"] = apiFormattedDate
1559
+ } else {
1560
+ print("Invalid date format in startDateText")
1561
+ }
1562
+ }
1563
+ }
1564
+
1565
+ params["interval"] = chosenPlan?.lowercased()
1566
+ }
1567
+
1568
+ do {
1569
+ let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
1570
+ uRLRequest.httpBody = jsonData
1571
+ if let jsonString = String(data: jsonData, encoding: .utf8) {
1572
+ print("JSON Payload: \(jsonString)")
1573
+ }
1574
+ } catch let error {
1575
+ print("Error creating JSON data: \(error)")
1576
+ hideLoadingIndicator()
1577
+ return
1578
+ }
1579
+
1580
+ let session = URLSession.shared
1581
+ let task = session.dataTask(with: uRLRequest) { (serviceData, serviceResponse, error) in
1582
+
1583
+ DispatchQueue.main.async {
1584
+ self.hideLoadingIndicator() // Stop loader when response is received
1585
+ }
1586
+
1587
+ if let error = error {
1588
+ print("Error: \(error.localizedDescription)")
1589
+ return
1590
+ }
1591
+
1592
+ guard let httpResponse = serviceResponse as? HTTPURLResponse else {
1593
+ print("Invalid response")
1594
+ return
1595
+ }
1596
+
1597
+ if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
1598
+ if let data = serviceData {
1599
+ do {
1600
+ if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
1601
+ print("Response Data: \(responseObject)")
1602
+
1603
+ // Check if status is 0 and handle the error
1604
+ if let status = responseObject["status"] as? Int, status == 0 {
1605
+ let errorMessage = responseObject["message"] as? String ?? "Unknown error"
1606
+ self.presentPaymentErrorVC(errorMessage: errorMessage)
1607
+ } else {
1608
+ DispatchQueue.main.async {
1609
+ if let paymentDoneVC = self.storyboard?.instantiateViewController(withIdentifier: "ThreeDSecurePaymentDoneVC") as? ThreeDSecurePaymentDoneVC {
1610
+
1611
+ let urlString = responseObject["redirect_url"] as? String ?? responseObject["location_url"] as? String ?? ""
1612
+ paymentDoneVC.redirectURL = urlString
1613
+ paymentDoneVC.chargeData = responseObject
1614
+ paymentDoneVC.easyPayDelegate = self.easyPayDelegate
1615
+
1616
+ self.navigationController?.pushViewController(paymentDoneVC, animated: true)
1617
+ }
1618
+ }
1619
+ }
1620
+ } else {
1621
+ self.presentPaymentErrorVC(errorMessage: "Invalid JSON format")
1622
+ }
1623
+ } catch let jsonError {
1624
+ self.presentPaymentErrorVC(errorMessage: "Error parsing JSON: \(jsonError)")
1625
+ }
1626
+ } else {
1627
+ self.presentPaymentErrorVC(errorMessage: "No data received")
1628
+ }
1629
+ } else {
1630
+ if let data = serviceData,
1631
+ let responseObj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
1632
+ let message = responseObj["message"] as? String {
1633
+ self.presentPaymentErrorVC(errorMessage: message)
1634
+ } else {
1635
+ self.presentPaymentErrorVC(errorMessage: "HTTP Status Code: \(httpResponse.statusCode)")
1636
+ }
1637
+ }
1638
+ }
1639
+ task.resume()
1640
+ }
1641
+
1112
1642
  }
1113
1643
 
1114
1644
  extension AdditionalInfoVC: UITextFieldDelegate {