@jimrising/easymerchantsdk-react-native 1.3.4 → 1.3.6

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 (47) 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/checksums/sha1-checksums.bin +0 -0
  10. package/android/.gradle/8.9/dependencies-accessors/gc.properties +0 -0
  11. package/android/.gradle/8.9/fileChanges/last-build.bin +0 -0
  12. package/android/.gradle/8.9/fileHashes/fileHashes.lock +0 -0
  13. package/android/.gradle/8.9/gc.properties +0 -0
  14. package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
  15. package/android/.gradle/buildOutputCleanup/cache.properties +2 -0
  16. package/android/.gradle/nb-cache/trust/0B5D6BE682AD6AEE9815EC13516BF075752CAE5AD5BECDCC00315C37622C2FD3 +1 -0
  17. package/android/.gradle/nb-cache/trust/23843E1876B2E51C07E80AB52D1E797E5D8053D8097EEEB15FB63DD903195C14 +1 -0
  18. package/android/.gradle/vcs-1/gc.properties +0 -0
  19. package/android/.settings/org.eclipse.buildship.core.prefs +2 -0
  20. package/ios/Classes/EasyMerchantSdk.m +43 -28
  21. package/ios/Classes/EasyMerchantSdk.swift +68 -9
  22. package/ios/CustomComponents/PlanSelector.swift +28 -30
  23. package/ios/EnvironmentConfig.swift +30 -30
  24. package/ios/Example/ViewController.swift +47 -51
  25. package/ios/Models/AdditionalInfo.swift +43 -6
  26. package/ios/Models/BillingInfo.swift +50 -6
  27. package/ios/Models/RecurringIntervals.swift +34 -0
  28. package/ios/Models/RecurringStartDateType.swift +13 -0
  29. package/ios/Models/Request.swift +120 -11
  30. package/ios/Pods/ViewControllers/AdditionalInfoVC.swift +609 -79
  31. package/ios/Pods/ViewControllers/BillingInfoVC/BillingInfoVC.swift +72 -25
  32. package/ios/Pods/ViewControllers/EmailVerificationVC.swift +14 -0
  33. package/ios/Pods/ViewControllers/OTPVerificationVC.swift +459 -42
  34. package/ios/Pods/ViewControllers/PaymentDoneVC.swift +16 -2
  35. package/ios/Pods/ViewControllers/PaymentErrorVC.swift +0 -2
  36. package/ios/Pods/ViewControllers/PaymentInformation/PaymentInfoVC.swift +1553 -372
  37. package/ios/Pods/ViewControllers/PaymentInformation/SavedAccountsTVC/SavedAccountTVC.swift +6 -2
  38. package/ios/Pods/ViewControllers/PaymentInformation/SavedCardsTVC/SavedCardsTVC.swift +6 -1
  39. package/ios/Pods/ViewControllers/ThreeDSecurePaymentDoneVC.swift +105 -0
  40. package/ios/easymerchantsdk.podspec +1 -1
  41. package/ios/easymerchantsdk.storyboard +554 -57
  42. package/package.json +2 -2
  43. package/.idea/caches/deviceStreaming.xml +0 -571
  44. package/.idea/em-MobileCheckoutSDK-ReactNative.iml +0 -9
  45. package/.idea/misc.xml +0 -5
  46. package/.idea/modules.xml +0 -8
  47. package/.idea/vcs.xml +0 -6
@@ -196,6 +196,10 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
196
196
  @IBOutlet weak var imgGrailPayAbandonError: UIImageView!
197
197
  @IBOutlet weak var lblGrailPayAabandonError: UILabel!
198
198
 
199
+ @IBOutlet weak var txtFieldChosePlanGrailPayBankView: TextFieldStackView!
200
+ @IBOutlet weak var txtFieldSelectDateGrailPayBankView: TextFieldStackView!
201
+ @IBOutlet weak var bankIconGrailPayBankView: UIImageView!
202
+
199
203
  //New GrailPay Account
200
204
  @IBOutlet weak var viewAddNewGrailPayAccount: UIView!
201
205
  @IBOutlet weak var lblGrailPayNewAccount: UILabel!
@@ -216,6 +220,10 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
216
220
  @IBOutlet weak var lblTermsAndConditionNewGrailPayAccountView: UILabel!
217
221
  @IBOutlet weak var btnChangeNewGrailPayAccountView: UIButton!
218
222
 
223
+ @IBOutlet weak var txtFieldChosePlanNewGrailPayBankView: TextFieldStackView!
224
+ @IBOutlet weak var txtFieldSelectDateNewGrailPayBankView: TextFieldStackView!
225
+ @IBOutlet weak var imgViewBankIconGrailPayNewBank: UIImageView!
226
+
219
227
  //SavedBank
220
228
  @IBOutlet weak var viewSingleSavedAccount: UIView!
221
229
  @IBOutlet weak var viewTermAndConditionsSingleAccountView: UIStackView!
@@ -345,7 +353,7 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
345
353
  var isFrom = String()
346
354
 
347
355
  //Blink Card
348
- // var blinkCardRecognizer: MBCBlinkCardRecognizer!
356
+ // var blinkCardRecognizer: MBCBlinkCardRecognizer!
349
357
 
350
358
  // Variables for Card Scanning Data Back
351
359
  var cardNumber: String?
@@ -378,8 +386,6 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
378
386
  var selectedNewGrailPayAccountName: String?
379
387
  var grailPayNewAccountCustomerID: String?
380
388
 
381
- // let planOptions = ["Weekly", "Monthly"]
382
-
383
389
  var startDatePickerHandler: DatePickerHandler?
384
390
 
385
391
  //MARK: - View Did Load
@@ -518,33 +524,50 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
518
524
  func viewAppearanceOn() {
519
525
  viewTermAndConditionsSingleAccountView.isHidden = true
520
526
  btnPayNowSingleAccountView.isHidden = true
521
- // viewSingleAccountViewHeight.constant = 68
522
-
523
527
  viewNewBankAccount.isHidden = true
524
-
525
528
  viewTxtFieldCVVSingleCard.isHidden = true
526
529
  btnPayNowSingleCard.isHidden = true
527
-
528
530
  self.viewCrypto.isHidden = true
529
531
 
530
- if request.enableRecurring == true {
532
+ if request.is_recurring == true {
531
533
  self.txtFieldChosePlanCard.isHidden = false
532
- self.txtFieldStartDateCard.isHidden = false
534
+ if request.recurringStartDateType == .custom {
535
+ self.txtFieldStartDateCard.isHidden = false
536
+ self.txtFieldSelectDateSingleSavedCard.isHidden = false
537
+ self.txtFieldSelectDateNewCardView.isHidden = false
538
+ self.txtFieldSelectDateViewBankFields.isHidden = false
539
+ self.txtFieldSelectDateSingleSavedBankView.isHidden = false
540
+ self.txtFieldSelectDateNewAccountView.isHidden = false
541
+ self.txtFieldSelectDateGrailPayBankView.isHidden = false
542
+ self.txtFieldSelectDateNewGrailPayBankView.isHidden = false
543
+
544
+ self.heightViewBankFields.constant = 696
545
+ self.heightViewNewCardFields.constant = 624
546
+ self.heightViewNewAccountFields.constant = 808
547
+ self.heightSubViewNewAccountFields.constant = 738
548
+ } else {
549
+ self.txtFieldStartDateCard.isHidden = true
550
+ self.txtFieldSelectDateSingleSavedCard.isHidden = true
551
+ self.txtFieldSelectDateNewCardView.isHidden = true
552
+ self.txtFieldSelectDateViewBankFields.isHidden = true
553
+ self.txtFieldSelectDateSingleSavedBankView.isHidden = true
554
+ self.txtFieldSelectDateNewAccountView.isHidden = true
555
+ self.txtFieldSelectDateGrailPayBankView.isHidden = true
556
+ self.txtFieldSelectDateNewGrailPayBankView.isHidden = true
557
+
558
+ self.heightViewBankFields.constant = 600
559
+ self.heightViewNewCardFields.constant = 529
560
+ self.heightViewNewAccountFields.constant = 713
561
+ self.heightSubViewNewAccountFields.constant = 643
562
+ }
563
+
533
564
  self.txtFieldSelectPlanSingleSavedCard.isHidden = false
534
- self.txtFieldSelectDateSingleSavedCard.isHidden = false
535
565
  self.txtFieldSelectPlanNewCardView.isHidden = false
536
- self.txtFieldSelectDateNewCardView.isHidden = false
537
566
  self.txtFieldSelectPlanViewBankFields.isHidden = false
538
- self.txtFieldSelectDateViewBankFields.isHidden = false
539
567
  self.txtFieldSelectPlanSingleSavedBankView.isHidden = false
540
- self.txtFieldSelectDateSingleSavedBankView.isHidden = false
541
568
  self.txtFieldSelectPlanNewAccountView.isHidden = false
542
- self.txtFieldSelectDateNewAccountView.isHidden = false
543
-
544
- self.heightViewBankFields.constant = 696
545
- self.heightViewNewCardFields.constant = 624
546
- self.heightViewNewAccountFields.constant = 808
547
- self.heightSubViewNewAccountFields.constant = 738
569
+ self.txtFieldChosePlanGrailPayBankView.isHidden = false
570
+ self.txtFieldChosePlanNewGrailPayBankView.isHidden = false
548
571
  }
549
572
  else {
550
573
  self.txtFieldChosePlanCard.isHidden = true
@@ -559,6 +582,10 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
559
582
  self.txtFieldSelectDateSingleSavedBankView.isHidden = true
560
583
  self.txtFieldSelectPlanNewAccountView.isHidden = true
561
584
  self.txtFieldSelectDateNewAccountView.isHidden = true
585
+ self.txtFieldChosePlanGrailPayBankView.isHidden = true
586
+ self.txtFieldChosePlanNewGrailPayBankView.isHidden = true
587
+ self.txtFieldSelectDateGrailPayBankView.isHidden = true
588
+ self.txtFieldSelectDateNewGrailPayBankView.isHidden = true
562
589
 
563
590
  self.heightViewBankFields.constant = 506
564
591
  self.heightViewNewCardFields.constant = 434
@@ -612,9 +639,14 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
612
639
  btnNext.isHidden = true
613
640
  btnNextHeight.constant = 0
614
641
  btnNextTopCon.constant = 0
615
- if request.enableRecurring == true {
642
+ if request.is_recurring == true {
616
643
  txtFieldSelectPlanSingleSavedCard.isHidden = false
617
- txtFieldSelectDateSingleSavedCard.isHidden = false
644
+ if request.recurringStartDateType == .custom {
645
+ txtFieldSelectDateSingleSavedCard.isHidden = false
646
+ }
647
+ else {
648
+ txtFieldSelectDateSingleSavedCard.isHidden = true
649
+ }
618
650
  }
619
651
  else {
620
652
  txtFieldSelectPlanSingleSavedCard.isHidden = true
@@ -687,9 +719,14 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
687
719
  if self.isSelectForPayBank {
688
720
  self.viewTermAndConditionsSingleAccountView.isHidden = false
689
721
  self.btnPayNowSingleAccountView.isHidden = false
690
- if request.enableRecurring == true {
722
+ if request.is_recurring == true {
691
723
  self.txtFieldSelectPlanSingleSavedBankView.isHidden = false
692
- self.txtFieldSelectDateSingleSavedBankView.isHidden = false
724
+ if request.recurringStartDateType == .custom {
725
+ self.txtFieldSelectDateSingleSavedBankView.isHidden = false
726
+ }
727
+ else {
728
+ self.txtFieldSelectDateSingleSavedBankView.isHidden = true
729
+ }
693
730
  }
694
731
  else {
695
732
  self.txtFieldSelectPlanSingleSavedBankView.isHidden = true
@@ -734,8 +771,8 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
734
771
  }
735
772
  }
736
773
  else {
737
- // self.viewBankFields.isHidden = false
738
- // self.viewTermsAndConditions.isHidden = false
774
+ // self.viewBankFields.isHidden = false
775
+ // self.viewTermsAndConditions.isHidden = false
739
776
  self.grailPayBankLinkView.isHidden = true
740
777
  }
741
778
  }
@@ -799,8 +836,8 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
799
836
  tblViewAccountTypes.backgroundColor = uiColor
800
837
  viewAccountTypeNewAccountView.backgroundColor = uiColor
801
838
  tblViewAccountTypeNewAccountView.backgroundColor = uiColor
802
- // viewTblViewRecurring.backgroundColor = uiColor
803
- // tblViewRecurring.backgroundColor = uiColor
839
+ // viewTblViewRecurring.backgroundColor = uiColor
840
+ // tblViewRecurring.backgroundColor = uiColor
804
841
  }
805
842
 
806
843
  if let bodyBackGroundColor = UserStoreSingleton.shared.body_bg_col,
@@ -970,7 +1007,7 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
970
1007
  if let primaryFontColor = UserStoreSingleton.shared.primary_font_col,
971
1008
  let uiColor = UIColor(hex: primaryFontColor) {
972
1009
  lblChosePaymentMethod.textColor = uiColor
973
- lblGrailPayNewAccount.tintColor = uiColor
1010
+ lblGrailPayNewAccount.textColor = uiColor
974
1011
  subLblGrailPayNewAccount.textColor = uiColor
975
1012
  lblSavedInfoEmailView.textColor = uiColor
976
1013
  subLblSavedEmailInfo.textColor = uiColor
@@ -989,6 +1026,11 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
989
1026
  lblCreateNewCard.textColor = uiColor
990
1027
  lblAccountNumberSingleAccountView.textColor = uiColor
991
1028
  bankIconImgView.tintColor = uiColor
1029
+ imgViewSigleSavedCard.tintColor = uiColor
1030
+ imgViewSigleSavedCard.tintColor = uiColor
1031
+ bankIconGrailPayBankView.tintColor = uiColor
1032
+ imgViewBankIconGrailPayNewBank.tintColor = uiColor
1033
+ imgViewCardUpdateCardView.tintColor = uiColor
992
1034
  lblChangeAccount.textColor = uiColor
993
1035
  subLblChangeAccount.textColor = uiColor
994
1036
  lblNewAccount.textColor = uiColor
@@ -1505,7 +1547,12 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
1505
1547
  // self.viewTblViewRecurring.isHidden.toggle()
1506
1548
  // }
1507
1549
 
1508
- PlanSelector.presentPlanOptions(from: self, sourceView: sender) { selectedPlan in
1550
+ guard let plans = request?.recurringIntervals, !plans.isEmpty else {
1551
+ print("No recurring intervals available.")
1552
+ return
1553
+ }
1554
+
1555
+ PlanSelector.presentPlanOptions(from: self, sourceView: sender, allowedPlans: plans) { selectedPlan in
1509
1556
  self.txtFieldChosePlanCard.text = selectedPlan
1510
1557
  }
1511
1558
  }
@@ -1519,7 +1566,12 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
1519
1566
  }
1520
1567
 
1521
1568
  @IBAction func actionBtnSelectPlanSingleSavedCard(_ sender: UIButton) {
1522
- PlanSelector.presentPlanOptions(from: self, sourceView: sender) { selectedPlan in
1569
+ guard let plans = request?.recurringIntervals, !plans.isEmpty else {
1570
+ print("No recurring intervals available.")
1571
+ return
1572
+ }
1573
+
1574
+ PlanSelector.presentPlanOptions(from: self, sourceView: sender, allowedPlans: plans) { selectedPlan in
1523
1575
  self.txtFieldSelectPlanSingleSavedCard.text = selectedPlan
1524
1576
  }
1525
1577
  }
@@ -1533,7 +1585,12 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
1533
1585
  }
1534
1586
 
1535
1587
  @IBAction func actionBtnSelectPlanNewCardView(_ sender: UIButton) {
1536
- PlanSelector.presentPlanOptions(from: self, sourceView: sender) { selectedPlan in
1588
+ guard let plans = request?.recurringIntervals, !plans.isEmpty else {
1589
+ print("No recurring intervals available.")
1590
+ return
1591
+ }
1592
+
1593
+ PlanSelector.presentPlanOptions(from: self, sourceView: sender, allowedPlans: plans) { selectedPlan in
1537
1594
  self.txtFieldSelectPlanNewCardView.text = selectedPlan
1538
1595
  }
1539
1596
  }
@@ -1547,7 +1604,12 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
1547
1604
  }
1548
1605
 
1549
1606
  @IBAction func actionBtnSelectPlanViewBankFields(_ sender: UIButton) {
1550
- PlanSelector.presentPlanOptions(from: self, sourceView: sender) { selectedPlan in
1607
+ guard let plans = request?.recurringIntervals, !plans.isEmpty else {
1608
+ print("No recurring intervals available.")
1609
+ return
1610
+ }
1611
+
1612
+ PlanSelector.presentPlanOptions(from: self, sourceView: sender, allowedPlans: plans) { selectedPlan in
1551
1613
  self.txtFieldSelectPlanViewBankFields.text = selectedPlan
1552
1614
  }
1553
1615
  }
@@ -1561,7 +1623,12 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
1561
1623
  }
1562
1624
 
1563
1625
  @IBAction func actionBtnSelectPlanSingleSavedAccount(_ sender: UIButton) {
1564
- PlanSelector.presentPlanOptions(from: self, sourceView: sender) { selectedPlan in
1626
+ guard let plans = request?.recurringIntervals, !plans.isEmpty else {
1627
+ print("No recurring intervals available.")
1628
+ return
1629
+ }
1630
+
1631
+ PlanSelector.presentPlanOptions(from: self, sourceView: sender, allowedPlans: plans) { selectedPlan in
1565
1632
  self.txtFieldSelectPlanSingleSavedBankView.text = selectedPlan
1566
1633
  }
1567
1634
  }
@@ -1575,7 +1642,12 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
1575
1642
  }
1576
1643
 
1577
1644
  @IBAction func actionBtnSelectPlanNewAccountView(_ sender: UIButton) {
1578
- PlanSelector.presentPlanOptions(from: self, sourceView: sender) { selectedPlan in
1645
+ guard let plans = request?.recurringIntervals, !plans.isEmpty else {
1646
+ print("No recurring intervals available.")
1647
+ return
1648
+ }
1649
+
1650
+ PlanSelector.presentPlanOptions(from: self, sourceView: sender, allowedPlans: plans) { selectedPlan in
1579
1651
  self.txtFieldSelectPlanNewAccountView.text = selectedPlan
1580
1652
  }
1581
1653
  }
@@ -1591,6 +1663,11 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
1591
1663
  @IBAction func actionBtnClose(_ sender: UIButton) {
1592
1664
  UserStoreSingleton.shared.clearUserData()
1593
1665
  UserStoreSingleton.shared.isLoggedIn = false
1666
+
1667
+ // Notify delegate about cancellation with custom message
1668
+ let result = Result(type: .cancelled, chargeData: ["message": "You’ve exited the payment screen. No transaction was made."])
1669
+ self.delegate?.easyPayController(self.navigationController as! EasyPayViewController, didFinishWith: result)
1670
+
1594
1671
  self.dismiss(animated: true)
1595
1672
  }
1596
1673
 
@@ -1620,6 +1697,12 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
1620
1697
  }
1621
1698
 
1622
1699
  @IBAction func actionBtnSelectSingleSavedCard(_ sender: UIButton) {
1700
+ // Use the currently selectedCardIndex instead of sender.tag
1701
+ guard let index = selectedCardIndex, index < savedCards.count else {
1702
+ showAlert(title: "No Card Selected", message: "Please select a card from the list first.")
1703
+ return
1704
+ }
1705
+
1623
1706
  // Toggle selection state
1624
1707
  isSelectForPay.toggle()
1625
1708
 
@@ -1635,13 +1718,15 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
1635
1718
  btnNextTopCon.constant = isSelectForPay ? 0 : 8
1636
1719
 
1637
1720
  // Handle recurring fields visibility
1638
- let showRecurringFields = isSelectForPay && request.enableRecurring == true
1721
+ let showRecurringFields = isSelectForPay && request.is_recurring == true
1722
+ let isCustomRecurring = request.recurringStartDateType == .custom
1639
1723
  txtFieldSelectPlanSingleSavedCard.isHidden = !showRecurringFields
1640
- txtFieldSelectDateSingleSavedCard.isHidden = !showRecurringFields
1724
+ txtFieldSelectDateSingleSavedCard.isHidden = !(showRecurringFields && isCustomRecurring)
1641
1725
 
1642
- // Set selected card
1643
- if isSelectForPay, let firstCard = savedCards.first {
1644
- selectedCard = firstCard
1726
+ // Set selected card based on tapped index
1727
+ if isSelectForPay, index < savedCards.count {
1728
+ selectedCard = savedCards[index]
1729
+ selectedCardIndex = index // Optional: track selected index
1645
1730
  }
1646
1731
  }
1647
1732
 
@@ -1678,8 +1763,25 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
1678
1763
 
1679
1764
  if cvvText.isEmpty {
1680
1765
  self.showAlert(title: "Missing CVV", message: "Please enter the CVV to proceed.")
1766
+ return
1681
1767
  } else if cvvText.count < 3 {
1682
1768
  self.showAlert(title: "Invalid CVV", message: "CVV must be at least 3 digits.")
1769
+ return
1770
+ }
1771
+
1772
+ // Recurring Plan + Date Validation (only if is_recurring is true)
1773
+ if let req = self.request, req.is_recurring == true {
1774
+ if txtFieldSelectPlanSingleSavedCard.text.isEmpty {
1775
+ self.showAlert(title: "Missing Information", message: "Please choose your plan.")
1776
+ return
1777
+ }
1778
+
1779
+ if let recurringType = req.recurringStartDateType, recurringType == .custom {
1780
+ if txtFieldSelectDateSingleSavedCard.text.isEmpty {
1781
+ self.showAlert(title: "Missing Information", message: "Please select a plan start date.")
1782
+ return
1783
+ }
1784
+ }
1683
1785
  }
1684
1786
 
1685
1787
  // Instantiate BillingInfoVC and pass the selected card data and CVV text
@@ -1698,6 +1800,10 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
1698
1800
  billingInfoVC.billingInfoData = jsonDict
1699
1801
  }
1700
1802
 
1803
+ billingInfoVC.request = self.request
1804
+ billingInfoVC.chosenPlan = txtFieldSelectPlanSingleSavedCard.text
1805
+ billingInfoVC.startDate = txtFieldSelectDateSingleSavedCard.text
1806
+
1701
1807
  // Navigate to BillingInfoVC
1702
1808
  self.navigationController?.pushViewController(billingInfoVC, animated: true)
1703
1809
  }
@@ -1707,13 +1813,29 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
1707
1813
 
1708
1814
  if cvvText.isEmpty {
1709
1815
  self.showAlert(title: "Missing CVV", message: "Please enter the CVV to proceed.")
1710
- }
1711
- else if cvvText.count < 3 {
1816
+ return
1817
+ } else if cvvText.count < 3 {
1712
1818
  self.showAlert(title: "Invalid CVV", message: "CVV must be at least 3 digits.")
1819
+ return
1713
1820
  }
1714
- else {
1715
- paymentIntentFromShowSavedCardApi()
1821
+
1822
+ // Recurring Plan + Date Validation (only if is_recurring is true)
1823
+ if let req = self.request, req.is_recurring == true {
1824
+ if txtFieldSelectPlanSingleSavedCard.text.isEmpty {
1825
+ self.showAlert(title: "Missing Information", message: "Please choose your plan.")
1826
+ return
1827
+ }
1828
+
1829
+ if let recurringType = req.recurringStartDateType, recurringType == .custom {
1830
+ if txtFieldSelectDateSingleSavedCard.text.isEmpty {
1831
+ self.showAlert(title: "Missing Information", message: "Please select a plan start date.")
1832
+ return
1833
+ }
1834
+ }
1716
1835
  }
1836
+
1837
+ // All validations passed, call the API
1838
+ paymentIntentFromShowSavedCardApi()
1717
1839
  }
1718
1840
  }
1719
1841
 
@@ -1812,6 +1934,21 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
1812
1934
  return
1813
1935
  }
1814
1936
 
1937
+ // Recurring Plan + Date Validation (only if is_recurring is true)
1938
+ if let req = self.request, req.is_recurring == true {
1939
+ if txtFieldSelectPlanNewCardView.text.isEmpty {
1940
+ self.showAlert(title: "Missing Information", message: "Please choose your plan.")
1941
+ return
1942
+ }
1943
+
1944
+ if let recurringType = req.recurringStartDateType, recurringType == .custom {
1945
+ if txtFieldSelectDateNewCardView.text.isEmpty {
1946
+ self.showAlert(title: "Missing Information", message: "Please select a plan start date.")
1947
+ return
1948
+ }
1949
+ }
1950
+ }
1951
+
1815
1952
  // Instantiate BillingInfoVC and pass the card details
1816
1953
  let billingInfoVC = UIStoryboard(name: "easymerchantsdk", bundle: .easyPayBundle).instantiateViewController(withIdentifier: "BillingInfoVC") as! BillingInfoVC
1817
1954
 
@@ -1833,11 +1970,20 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
1833
1970
  billingInfoVC.billingInfoData = jsonDict
1834
1971
  }
1835
1972
 
1973
+ billingInfoVC.request = self.request
1974
+ billingInfoVC.chosenPlan = txtFieldSelectPlanNewCardView.text
1975
+ billingInfoVC.startDate = txtFieldSelectDateNewCardView.text
1976
+
1836
1977
  // Navigate to BillingInfoVC
1837
1978
  self.navigationController?.pushViewController(billingInfoVC, animated: true)
1838
1979
  }
1839
1980
  else {
1840
- paymentIntentFromAddNewCardApi(customerId: UserStoreSingleton.shared.customerId)
1981
+ if request.enable3DS == true {
1982
+ threeDSecurePaymentNewCardApi(customerId: UserStoreSingleton.shared.customerId ?? "")
1983
+ }
1984
+ else {
1985
+ paymentIntentFromAddNewCardApi(customerId: UserStoreSingleton.shared.customerId)
1986
+ }
1841
1987
  }
1842
1988
  }
1843
1989
  }
@@ -1894,6 +2040,44 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
1894
2040
  self.viewSingleSavedAccount.isHidden = false
1895
2041
  }
1896
2042
 
2043
+ @IBAction func actionBtnChosePlanGrailPayBankView(_ sender: UIButton) {
2044
+ guard let plans = request?.recurringIntervals, !plans.isEmpty else {
2045
+ print("No recurring intervals available.")
2046
+ return
2047
+ }
2048
+
2049
+ PlanSelector.presentPlanOptions(from: self, sourceView: sender, allowedPlans: plans) { selectedPlan in
2050
+ self.txtFieldChosePlanGrailPayBankView.text = selectedPlan
2051
+ }
2052
+ }
2053
+
2054
+ @IBAction func actionBtnSelectDateGrailPayBankView(_ sender: UIButton) {
2055
+ startDatePickerHandler = DatePickerHandler(textField: txtFieldSelectDateGrailPayBankView.textField)
2056
+ startDatePickerHandler?.onDateSelected = { selectedDate in
2057
+
2058
+ }
2059
+ txtFieldSelectDateGrailPayBankView.textField.becomeFirstResponder()
2060
+ }
2061
+
2062
+ @IBAction func actionBtnSelectPlanNewGrailPayBankView(_ sender: UIButton) {
2063
+ guard let plans = request?.recurringIntervals, !plans.isEmpty else {
2064
+ print("No recurring intervals available.")
2065
+ return
2066
+ }
2067
+
2068
+ PlanSelector.presentPlanOptions(from: self, sourceView: sender, allowedPlans: plans) { selectedPlan in
2069
+ self.txtFieldChosePlanNewGrailPayBankView.text = selectedPlan
2070
+ }
2071
+ }
2072
+
2073
+ @IBAction func actionBtnSelectDateNewGrailPayBankView(_ sender: UIButton) {
2074
+ startDatePickerHandler = DatePickerHandler(textField: txtFieldSelectDateNewGrailPayBankView.textField)
2075
+ startDatePickerHandler?.onDateSelected = { selectedDate in
2076
+
2077
+ }
2078
+ txtFieldSelectDateNewGrailPayBankView.textField.becomeFirstResponder()
2079
+ }
2080
+
1897
2081
  //MARK: - Account Connect Api
1898
2082
  func accountConnectApi(account: [String: Any]) {
1899
2083
  showLoadingIndicator()
@@ -1906,15 +2090,15 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
1906
2090
  return
1907
2091
  }
1908
2092
 
1909
- var request = URLRequest(url: serviceURL)
1910
- request.httpMethod = "POST"
1911
- request.addValue("application/json", forHTTPHeaderField: "Content-Type")
2093
+ var uRLRequest = URLRequest(url: serviceURL)
2094
+ uRLRequest.httpMethod = "POST"
2095
+ uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
1912
2096
 
1913
2097
  // Add API headers
1914
2098
  if let apiKey = EnvironmentConfig.apiKey,
1915
2099
  let apiSecret = EnvironmentConfig.apiSecret {
1916
- request.addValue(apiKey, forHTTPHeaderField: "x-api-key")
1917
- request.addValue(apiSecret, forHTTPHeaderField: "x-api-secret")
2100
+ uRLRequest.addValue(apiKey, forHTTPHeaderField: "x-api-key")
2101
+ uRLRequest.addValue(apiSecret, forHTTPHeaderField: "x-api-secret")
1918
2102
  }
1919
2103
 
1920
2104
  // Prepare parameters
@@ -1937,7 +2121,7 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
1937
2121
 
1938
2122
  do {
1939
2123
  let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
1940
- request.httpBody = jsonData
2124
+ uRLRequest.httpBody = jsonData
1941
2125
 
1942
2126
  if let jsonString = String(data: jsonData, encoding: .utf8) {
1943
2127
  print("🔍 JSON Payload:\n\(jsonString)")
@@ -1948,7 +2132,7 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
1948
2132
  return
1949
2133
  }
1950
2134
 
1951
- let task = URLSession.shared.dataTask(with: request) { data, response, error in
2135
+ let task = URLSession.shared.dataTask(with: uRLRequest) { data, response, error in
1952
2136
  DispatchQueue.main.async { self.hideLoadingIndicator() }
1953
2137
 
1954
2138
  if let error = error {
@@ -1986,6 +2170,16 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
1986
2170
  self.viewGrailPaySavedBank.isHidden = false
1987
2171
  self.viewGrailPaySavedAccount.isHidden = false
1988
2172
 
2173
+ if self.request.is_recurring == true {
2174
+ self.txtFieldChosePlanGrailPayBankView.isHidden = false
2175
+ if self.request.recurringStartDateType == .custom {
2176
+ self.txtFieldSelectDateGrailPayBankView.isHidden = false
2177
+ }
2178
+ else {
2179
+ self.txtFieldSelectDateGrailPayBankView.isHidden = true
2180
+ }
2181
+ }
2182
+
1989
2183
  if let accountNumber = account["account_number"] as? String {
1990
2184
  let last4 = String(accountNumber.suffix(4))
1991
2185
  self.lblGrailPaySavedAccountNumber.text = "****\(last4)"
@@ -2131,6 +2325,16 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
2131
2325
  self.grailPayNewSavedAccountView.isHidden = false
2132
2326
  self.viewGrailPayNewAccountDetail.isHidden = false
2133
2327
 
2328
+ if self.request.is_recurring == true {
2329
+ self.txtFieldChosePlanNewGrailPayBankView.isHidden = false
2330
+ if self.request.recurringStartDateType == .custom {
2331
+ self.txtFieldSelectDateNewGrailPayBankView.isHidden = false
2332
+ }
2333
+ else {
2334
+ self.txtFieldSelectDateNewGrailPayBankView.isHidden = true
2335
+ }
2336
+ }
2337
+
2134
2338
  if let accountNumber = account["account_number"] as? String {
2135
2339
  let last4 = String(accountNumber.suffix(4))
2136
2340
  self.lblGrailPayNewAccountNumber.text = "****\(last4)"
@@ -2177,6 +2381,21 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
2177
2381
 
2178
2382
  @IBAction private func actionBtnLinkGrailPayBankAccount(_ sender: UIButton) {
2179
2383
  if isBankAccountConnected {
2384
+ // Recurring Plan + Date Validation (only if is_recurring is true)
2385
+ if let req = self.request, req.is_recurring == true {
2386
+ if self.txtFieldChosePlanGrailPayBankView.text.isEmpty {
2387
+ self.showAlert(title: "Missing Information", message: "Please choose your plan.")
2388
+ return
2389
+ }
2390
+
2391
+ if let recurringType = req.recurringStartDateType, recurringType == .custom {
2392
+ if self.txtFieldSelectDateGrailPayBankView.text.isEmpty {
2393
+ self.showAlert(title: "Missing Information", message: "Please select start date.")
2394
+ return
2395
+ }
2396
+ }
2397
+ }
2398
+
2180
2399
  guard agreeTermsAndCondtition else {
2181
2400
  self.showAlert(title: "Terms and Conditions", message: "Please agree to the terms and conditions")
2182
2401
  return
@@ -2203,6 +2422,21 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
2203
2422
 
2204
2423
  @IBAction func actionLinkNewGrailPayAccount(_ sender: UIButton) {
2205
2424
  if isNewBankAccountConnected {
2425
+ // Recurring Plan + Date Validation (only if is_recurring is true)
2426
+ if let req = self.request, req.is_recurring == true {
2427
+ if self.txtFieldChosePlanNewGrailPayBankView.text.isEmpty {
2428
+ self.showAlert(title: "Missing Information", message: "Please choose your plan.")
2429
+ return
2430
+ }
2431
+
2432
+ if let recurringType = req.recurringStartDateType, recurringType == .custom {
2433
+ if self.txtFieldSelectDateNewGrailPayBankView.text.isEmpty {
2434
+ self.showAlert(title: "Missing Information", message: "Please select start date.")
2435
+ return
2436
+ }
2437
+ }
2438
+ }
2439
+
2206
2440
  guard agreeTermsAndCondtition else {
2207
2441
  self.showAlert(title: "Terms and Conditions", message: "Please agree to the terms and conditions")
2208
2442
  return
@@ -2316,22 +2550,22 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
2316
2550
  return
2317
2551
  }
2318
2552
 
2319
- var request = URLRequest(url: serviceURL)
2320
- request.httpMethod = "POST"
2321
- request.addValue("application/json", forHTTPHeaderField: "Content-Type")
2553
+ var uRLRequest = URLRequest(url: serviceURL)
2554
+ uRLRequest.httpMethod = "POST"
2555
+ uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
2322
2556
 
2323
2557
  let token = UserStoreSingleton.shared.clientToken
2324
2558
  print("Setting clientToken header: \(token ?? "None")")
2325
- request.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
2559
+ uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
2326
2560
 
2327
2561
  if let apiKey = EnvironmentConfig.apiKey {
2328
- request.addValue(apiKey, forHTTPHeaderField: "X-Api-Key")
2562
+ uRLRequest.addValue(apiKey, forHTTPHeaderField: "X-Api-Key")
2329
2563
  }
2330
2564
  if let apiSecret = EnvironmentConfig.apiSecret {
2331
- request.addValue(apiSecret, forHTTPHeaderField: "X-Api-Secret")
2565
+ uRLRequest.addValue(apiSecret, forHTTPHeaderField: "X-Api-Secret")
2332
2566
  }
2333
2567
 
2334
- let params: [String: Any] = [
2568
+ var params: [String: Any] = [
2335
2569
  "account_id": self.grailPayAccountID ?? "",
2336
2570
  "account_type": self.selectedGrailPayAccountType ?? "",
2337
2571
  "name": self.selectedGrailPayAccountName ?? "",
@@ -2339,11 +2573,34 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
2339
2573
  "email": "newmerchantadminuser12@test.com"
2340
2574
  ]
2341
2575
 
2576
+ // Add these if recurring is enabled
2577
+ if let req = request, req.is_recurring == true {
2578
+ if let recurringType = req.recurringStartDateType, recurringType == .custom {
2579
+ // Only send start_date if type is .custom and field is not empty
2580
+ if let startDateText = txtFieldSelectDateGrailPayBankView?.text, !startDateText.isEmpty {
2581
+ let inputFormatter = DateFormatter()
2582
+ inputFormatter.dateFormat = "dd/MM/yyyy"
2583
+
2584
+ let outputFormatter = DateFormatter()
2585
+ outputFormatter.dateFormat = "MM/dd/yyyy"
2586
+
2587
+ if let date = inputFormatter.date(from: startDateText) {
2588
+ let apiFormattedDate = outputFormatter.string(from: date)
2589
+ params["start_date"] = apiFormattedDate
2590
+ } else {
2591
+ print("Invalid date format in startDateText")
2592
+ }
2593
+ }
2594
+ }
2595
+
2596
+ params["interval"] = txtFieldChosePlanGrailPayBankView.text.lowercased()
2597
+ }
2598
+
2342
2599
  print(params)
2343
2600
 
2344
2601
  do {
2345
2602
  let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
2346
- request.httpBody = jsonData
2603
+ uRLRequest.httpBody = jsonData
2347
2604
  if let jsonString = String(data: jsonData, encoding: .utf8) {
2348
2605
  print("JSON Payload: \(jsonString)")
2349
2606
  }
@@ -2354,7 +2611,7 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
2354
2611
  }
2355
2612
 
2356
2613
  let session = URLSession.shared
2357
- let task = session.dataTask(with: request) { (serviceData, serviceResponse, error) in
2614
+ let task = session.dataTask(with: uRLRequest) { (serviceData, serviceResponse, error) in
2358
2615
 
2359
2616
  DispatchQueue.main.async {
2360
2617
  self.hideLoadingIndicator() // Stop loader when response is received
@@ -2424,22 +2681,22 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
2424
2681
  return
2425
2682
  }
2426
2683
 
2427
- var request = URLRequest(url: serviceURL)
2428
- request.httpMethod = "POST"
2429
- request.addValue("application/json", forHTTPHeaderField: "Content-Type")
2684
+ var uRLRequest = URLRequest(url: serviceURL)
2685
+ uRLRequest.httpMethod = "POST"
2686
+ uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
2430
2687
 
2431
2688
  let token = UserStoreSingleton.shared.clientToken
2432
2689
  print("Setting clientToken header: \(token ?? "None")")
2433
- request.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
2690
+ uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
2434
2691
 
2435
2692
  if let apiKey = EnvironmentConfig.apiKey {
2436
- request.addValue(apiKey, forHTTPHeaderField: "X-Api-Key")
2693
+ uRLRequest.addValue(apiKey, forHTTPHeaderField: "X-Api-Key")
2437
2694
  }
2438
2695
  if let apiSecret = EnvironmentConfig.apiSecret {
2439
- request.addValue(apiSecret, forHTTPHeaderField: "X-Api-Secret")
2696
+ uRLRequest.addValue(apiSecret, forHTTPHeaderField: "X-Api-Secret")
2440
2697
  }
2441
2698
 
2442
- let params: [String: Any] = [
2699
+ var params: [String: Any] = [
2443
2700
  "account_id": self.newGrailPayAccountID ?? "",
2444
2701
  "account_type": self.selectedNewGrailPayAccountType ?? "",
2445
2702
  "name": self.selectedNewGrailPayAccountName ?? "",
@@ -2447,11 +2704,34 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
2447
2704
  "customer_id": self.grailPayNewAccountCustomerID ?? ""
2448
2705
  ]
2449
2706
 
2707
+ // Add these if recurring is enabled
2708
+ if let req = request, req.is_recurring == true {
2709
+ if let recurringType = req.recurringStartDateType, recurringType == .custom {
2710
+ // Only send start_date if type is .custom and field is not empty
2711
+ if let startDateText = txtFieldSelectDateNewGrailPayBankView?.text, !startDateText.isEmpty {
2712
+ let inputFormatter = DateFormatter()
2713
+ inputFormatter.dateFormat = "dd/MM/yyyy"
2714
+
2715
+ let outputFormatter = DateFormatter()
2716
+ outputFormatter.dateFormat = "MM/dd/yyyy"
2717
+
2718
+ if let date = inputFormatter.date(from: startDateText) {
2719
+ let apiFormattedDate = outputFormatter.string(from: date)
2720
+ params["start_date"] = apiFormattedDate
2721
+ } else {
2722
+ print("Invalid date format in startDateText")
2723
+ }
2724
+ }
2725
+ }
2726
+
2727
+ params["interval"] = txtFieldChosePlanNewGrailPayBankView.text.lowercased()
2728
+ }
2729
+
2450
2730
  print(params)
2451
2731
 
2452
2732
  do {
2453
2733
  let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
2454
- request.httpBody = jsonData
2734
+ uRLRequest.httpBody = jsonData
2455
2735
  if let jsonString = String(data: jsonData, encoding: .utf8) {
2456
2736
  print("JSON Payload: \(jsonString)")
2457
2737
  }
@@ -2462,7 +2742,7 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
2462
2742
  }
2463
2743
 
2464
2744
  let session = URLSession.shared
2465
- let task = session.dataTask(with: request) { (serviceData, serviceResponse, error) in
2745
+ let task = session.dataTask(with: uRLRequest) { (serviceData, serviceResponse, error) in
2466
2746
 
2467
2747
  DispatchQueue.main.async {
2468
2748
  self.hideLoadingIndicator() // Stop loader when response is received
@@ -2532,22 +2812,22 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
2532
2812
  return
2533
2813
  }
2534
2814
 
2535
- var request = URLRequest(url: serviceURL)
2536
- request.httpMethod = "POST"
2537
- request.addValue("application/json", forHTTPHeaderField: "Content-Type")
2815
+ var uRLRequest = URLRequest(url: serviceURL)
2816
+ uRLRequest.httpMethod = "POST"
2817
+ uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
2538
2818
 
2539
2819
  let token = UserStoreSingleton.shared.clientToken
2540
2820
  print("Setting clientToken header: \(token ?? "None")")
2541
- request.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
2821
+ uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
2542
2822
 
2543
2823
  if let apiKey = EnvironmentConfig.apiKey {
2544
- request.addValue(apiKey, forHTTPHeaderField: "X-Api-Key")
2824
+ uRLRequest.addValue(apiKey, forHTTPHeaderField: "X-Api-Key")
2545
2825
  }
2546
2826
  if let apiSecret = EnvironmentConfig.apiSecret {
2547
- request.addValue(apiSecret, forHTTPHeaderField: "X-Api-Secret")
2827
+ uRLRequest.addValue(apiSecret, forHTTPHeaderField: "X-Api-Secret")
2548
2828
  }
2549
2829
 
2550
- let params: [String: Any] = [
2830
+ var params: [String: Any] = [
2551
2831
  "account_id": self.newGrailPayAccountID ?? "",
2552
2832
  "account_type": self.selectedNewGrailPayAccountType ?? "",
2553
2833
  "name": self.selectedNewGrailPayAccountName ?? "",
@@ -2557,11 +2837,34 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
2557
2837
  "is_default": 1
2558
2838
  ]
2559
2839
 
2840
+ // Add these if recurring is enabled
2841
+ if let req = request, req.is_recurring == true {
2842
+ if let recurringType = req.recurringStartDateType, recurringType == .custom {
2843
+ // Only send start_date if type is .custom and field is not empty
2844
+ if let startDateText = txtFieldSelectDateNewGrailPayBankView?.text, !startDateText.isEmpty {
2845
+ let inputFormatter = DateFormatter()
2846
+ inputFormatter.dateFormat = "dd/MM/yyyy"
2847
+
2848
+ let outputFormatter = DateFormatter()
2849
+ outputFormatter.dateFormat = "MM/dd/yyyy"
2850
+
2851
+ if let date = inputFormatter.date(from: startDateText) {
2852
+ let apiFormattedDate = outputFormatter.string(from: date)
2853
+ params["start_date"] = apiFormattedDate
2854
+ } else {
2855
+ print("Invalid date format in startDateText")
2856
+ }
2857
+ }
2858
+ }
2859
+
2860
+ params["interval"] = txtFieldChosePlanNewGrailPayBankView.text.lowercased()
2861
+ }
2862
+
2560
2863
  print(params)
2561
2864
 
2562
2865
  do {
2563
2866
  let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
2564
- request.httpBody = jsonData
2867
+ uRLRequest.httpBody = jsonData
2565
2868
  if let jsonString = String(data: jsonData, encoding: .utf8) {
2566
2869
  print("JSON Payload: \(jsonString)")
2567
2870
  }
@@ -2572,7 +2875,7 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
2572
2875
  }
2573
2876
 
2574
2877
  let session = URLSession.shared
2575
- let task = session.dataTask(with: request) { (serviceData, serviceResponse, error) in
2878
+ let task = session.dataTask(with: uRLRequest) { (serviceData, serviceResponse, error) in
2576
2879
 
2577
2880
  DispatchQueue.main.async {
2578
2881
  self.hideLoadingIndicator() // Stop loader when response is received
@@ -2643,33 +2946,56 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
2643
2946
  return
2644
2947
  }
2645
2948
 
2646
- var request = URLRequest(url: serviceURL)
2647
- request.httpMethod = "POST"
2648
- request.addValue("application/json", forHTTPHeaderField: "Content-Type")
2949
+ var uRLRequest = URLRequest(url: serviceURL)
2950
+ uRLRequest.httpMethod = "POST"
2951
+ uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
2649
2952
 
2650
2953
  let token = UserStoreSingleton.shared.clientToken
2651
2954
  print("Setting clientToken header: \(token ?? "None")")
2652
- request.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
2955
+ uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
2653
2956
 
2654
2957
  if let apiKey = EnvironmentConfig.apiKey {
2655
- request.addValue(apiKey, forHTTPHeaderField: "X-Api-Key")
2958
+ uRLRequest.addValue(apiKey, forHTTPHeaderField: "X-Api-Key")
2656
2959
  }
2657
2960
  if let apiSecret = EnvironmentConfig.apiSecret {
2658
- request.addValue(apiSecret, forHTTPHeaderField: "X-Api-Secret")
2961
+ uRLRequest.addValue(apiSecret, forHTTPHeaderField: "X-Api-Secret")
2659
2962
  }
2660
2963
 
2661
- let params: [String: Any] = [
2964
+ var params: [String: Any] = [
2662
2965
  "account_id": selectedbankAccounts?.account_id ?? "",
2663
2966
  "customer": selectedbankAccounts?.customer_id ?? "",
2664
2967
  "description": "payment checkout",
2665
2968
  "currency": "usd",
2666
2969
  ]
2667
2970
 
2971
+ // Add these if recurring is enabled
2972
+ if let req = request, req.is_recurring == true {
2973
+ if let recurringType = req.recurringStartDateType, recurringType == .custom {
2974
+ // Only send start_date if type is .custom and field is not empty
2975
+ if let startDateText = txtFieldSelectDateSingleSavedBankView?.text, !startDateText.isEmpty {
2976
+ let inputFormatter = DateFormatter()
2977
+ inputFormatter.dateFormat = "dd/MM/yyyy"
2978
+
2979
+ let outputFormatter = DateFormatter()
2980
+ outputFormatter.dateFormat = "MM/dd/yyyy"
2981
+
2982
+ if let date = inputFormatter.date(from: startDateText) {
2983
+ let apiFormattedDate = outputFormatter.string(from: date)
2984
+ params["start_date"] = apiFormattedDate
2985
+ } else {
2986
+ print("Invalid date format in startDateText")
2987
+ }
2988
+ }
2989
+ }
2990
+
2991
+ params["interval"] = txtFieldSelectPlanSingleSavedBankView.text.lowercased()
2992
+ }
2993
+
2668
2994
  print(params)
2669
2995
 
2670
2996
  do {
2671
2997
  let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
2672
- request.httpBody = jsonData
2998
+ uRLRequest.httpBody = jsonData
2673
2999
  if let jsonString = String(data: jsonData, encoding: .utf8) {
2674
3000
  print("JSON Payload: \(jsonString)")
2675
3001
  }
@@ -2680,7 +3006,7 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
2680
3006
  }
2681
3007
 
2682
3008
  let session = URLSession.shared
2683
- let task = session.dataTask(with: request) { (serviceData, serviceResponse, error) in
3009
+ let task = session.dataTask(with: uRLRequest) { (serviceData, serviceResponse, error) in
2684
3010
 
2685
3011
  DispatchQueue.main.async {
2686
3012
  self.hideLoadingIndicator() // Stop loader when response is received
@@ -2773,71 +3099,145 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
2773
3099
  // Validation checks
2774
3100
  if cardNumber.isEmpty {
2775
3101
  self.showAlert(title: "Missing Information", message: "Card Number is required.")
3102
+ return
2776
3103
  } else if sanitizedCardNumber.count != 16 {
2777
3104
  self.showAlert(title: "Invalid Card Number", message: "Card Number must be 16 digits.")
3105
+ return
2778
3106
  } else if expiryDate.isEmpty {
2779
3107
  self.showAlert(title: "Missing Information", message: "Card Expiry Date is required.")
2780
- } else if !expiryDate.matches(expiryDateRegex) {
3108
+ return
3109
+ }
3110
+ else if !expiryDate.matches(expiryDateRegex) {
2781
3111
  self.showAlert(title: "Invalid Expiry Date", message: "Expiry Date must be in format MM/yyyy (e.g., 02/2026).")
2782
- } else if cvv.isEmpty {
3112
+ return
3113
+ }
3114
+ else {
3115
+ // Check if expiry date is past tomorrow
3116
+ let dateFormatter = DateFormatter()
3117
+ dateFormatter.dateFormat = "MM/yyyy"
3118
+ dateFormatter.timeZone = TimeZone.current
3119
+ if let expDate = dateFormatter.date(from: expiryDate) {
3120
+ let calendar = Calendar.current
3121
+ // Get start of the month following expiry
3122
+ let expiryMonthStart = calendar.date(from: calendar.dateComponents([.year, .month], from: expDate))!
3123
+ let endOfMonth = calendar.date(byAdding: DateComponents(month: 1, day: -1), to: expiryMonthStart)!
3124
+
3125
+ let tomorrow = calendar.date(byAdding: .day, value: 1, to: Date())!
3126
+ if endOfMonth < tomorrow {
3127
+ self.showAlert(title: "Expired Card", message: "Card is expired or please enter a valid expiry date.")
3128
+ return
3129
+ }
3130
+ } else {
3131
+ self.showAlert(title: "Invalid Expiry Date", message: "Unable to parse expiry date.")
3132
+ return
3133
+ }
3134
+ }
3135
+
3136
+ if cvv.isEmpty {
2783
3137
  self.showAlert(title: "Missing Information", message: "Card CVV Number is required.")
3138
+ return
2784
3139
  } else if cvv.count < 3 {
2785
3140
  self.showAlert(title: "Invalid CVV", message: "CVV must be at least 3 digits.")
3141
+ return
2786
3142
  } else if cardName.isEmpty {
2787
3143
  self.showAlert(title: "Missing Information", message: "Card Name is required.")
3144
+ return
2788
3145
  } else if !cardName.matches(nameRegex) {
2789
3146
  self.showAlert(title: "Invalid Name", message: "Name must contain only letters and spaces.")
2790
- } else {
2791
- // Proceed to BillingInfoVC
2792
- let vc = easymerchantsdk.instantiateViewController(withIdentifier: "BillingInfoVC") as! BillingInfoVC
2793
- vc.billingInfoData = jsonDict
2794
- vc.cardNumber = cardNumber
2795
- vc.expiryDate = expiryDate
2796
- vc.cvv = cvv
2797
- vc.nameOnCard = cardName
2798
- vc.selectedPaymentMethod = self.selectedPaymentMethod
2799
- vc.isSavedForFuture = self.isSavedForFuture
2800
- self.navigationController?.pushViewController(vc, animated: true)
3147
+ return
3148
+ }
3149
+
3150
+ // Recurring Plan + Date Validation (only if is_recurring is true)
3151
+ if let req = self.request, req.is_recurring == true {
3152
+ if self.txtFieldChosePlanCard.text.isEmpty {
3153
+ self.showAlert(title: "Missing Information", message: "Please choose your plan.")
3154
+ return
3155
+ }
3156
+
3157
+ if let recurringType = req.recurringStartDateType, recurringType == .custom {
3158
+ if self.txtFieldStartDateCard.text.isEmpty {
3159
+ self.showAlert(title: "Missing Information", message: "Please select start date.")
3160
+ return
3161
+ }
3162
+ }
2801
3163
  }
3164
+
3165
+ // Proceed to BillingInfoVC
3166
+ let vc = easymerchantsdk.instantiateViewController(withIdentifier: "BillingInfoVC") as! BillingInfoVC
3167
+ vc.billingInfoData = jsonDict
3168
+ vc.cardNumber = cardNumber
3169
+ vc.expiryDate = expiryDate
3170
+ vc.cvv = cvv
3171
+ vc.nameOnCard = cardName
3172
+ vc.selectedPaymentMethod = self.selectedPaymentMethod
3173
+ vc.isSavedForFuture = self.isSavedForFuture
3174
+ vc.request = self.request
3175
+ vc.chosenPlan = self.txtFieldChosePlanCard.text
3176
+ vc.startDate = self.txtFieldStartDateCard.text
3177
+ self.navigationController?.pushViewController(vc, animated: true)
2802
3178
  }
2803
3179
  else if self.selectedPaymentMethod == "Bank" {
2804
3180
  // Bank Case
2805
3181
  if self.txtFieldAccountName.text?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == true {
2806
3182
  self.showAlert(title: "Missing Information", message: "Bank account name is required.")
3183
+ return
2807
3184
  } else if self.txtFieldRoutingNumber.text?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == true {
2808
3185
  self.showAlert(title: "Missing Information", message: "Routing number is required.")
3186
+ return
2809
3187
  }
2810
3188
  else if let routingNumber = self.txtFieldRoutingNumber.text?.replacingOccurrences(of: " ", with: ""), routingNumber.count != 9 || !CharacterSet.decimalDigits.isSuperset(of: CharacterSet(charactersIn: routingNumber)) {
2811
3189
  self.showAlert(title: "Invalid Routing Number", message: "Routing number is not correct. It must be exactly 9 digits.")
3190
+ return
2812
3191
  }
2813
3192
  else if self.txtFieldAccountType.text?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == true {
2814
3193
  self.showAlert(title: "Missing Information", message: "Bank account type is required.")
3194
+ return
2815
3195
  } else if self.txtFieldAccountNumber.text?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == true {
2816
3196
  self.showAlert(title: "Missing Information", message: "Bank account number is required.")
3197
+ return
2817
3198
  }
2818
3199
  else if self.txtFieldConfirmBankAccount.text?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == true {
2819
3200
  self.showAlert(title: "Missing Information", message: "Please confirm your bank account number.")
3201
+ return
2820
3202
  }
2821
3203
  else if self.txtFieldAccountNumber.text?.trimmingCharacters(in: .whitespacesAndNewlines) != self.txtFieldConfirmBankAccount.text?.trimmingCharacters(in: .whitespacesAndNewlines) {
2822
3204
  self.showAlert(title: "Account Mismatch", message: "Bank account number and confirmation do not match.")
3205
+ return
2823
3206
  }
2824
3207
  else if !self.agreeTermsAndCondtition {
2825
3208
  self.showAlert(title: "Terms and Conditions", message: "Please agree to the terms and conditions")
2826
3209
  return
2827
3210
  }
2828
- else {
2829
- // Proceed to BillingInfoVC
2830
- let vc = easymerchantsdk.instantiateViewController(withIdentifier: "BillingInfoVC") as! BillingInfoVC
2831
- vc.accountName = self.txtFieldAccountName.text
2832
- vc.routingNumber = self.txtFieldRoutingNumber.text
2833
- vc.accountType = self.txtFieldAccountType.text
2834
- vc.accountNumber = self.txtFieldAccountNumber.text
2835
- vc.billingInfoData = jsonDict
2836
- vc.selectedPaymentMethod = self.selectedPaymentMethod
2837
- vc.isSavedForFuture = self.isSavedForFuture
2838
- vc.isFrom = "NormalBankPayWithoutSave"
2839
- self.navigationController?.pushViewController(vc, animated: true)
3211
+
3212
+ // Recurring Plan + Date Validation (only if is_recurring is true)
3213
+ if let req = self.request, req.is_recurring == true {
3214
+ if self.txtFieldSelectPlanViewBankFields.text.isEmpty {
3215
+ self.showAlert(title: "Missing Information", message: "Please choose your plan.")
3216
+ return
3217
+ }
3218
+
3219
+ if let recurringType = req.recurringStartDateType, recurringType == .custom {
3220
+ if self.txtFieldSelectDateViewBankFields.text.isEmpty {
3221
+ self.showAlert(title: "Missing Information", message: "Please select start date.")
3222
+ return
3223
+ }
3224
+ }
2840
3225
  }
3226
+
3227
+ // Proceed to BillingInfoVC
3228
+ let vc = easymerchantsdk.instantiateViewController(withIdentifier: "BillingInfoVC") as! BillingInfoVC
3229
+ vc.accountName = self.txtFieldAccountName.text
3230
+ vc.routingNumber = self.txtFieldRoutingNumber.text
3231
+ vc.accountType = self.txtFieldAccountType.text
3232
+ vc.accountNumber = self.txtFieldAccountNumber.text
3233
+ vc.billingInfoData = jsonDict
3234
+ vc.selectedPaymentMethod = self.selectedPaymentMethod
3235
+ vc.isSavedForFuture = self.isSavedForFuture
3236
+ vc.isFrom = "NormalBankPayWithoutSave"
3237
+ vc.chosenPlan = self.txtFieldSelectPlanViewBankFields.text
3238
+ vc.startDate = self.txtFieldSelectDateViewBankFields.text
3239
+ vc.request = self.request
3240
+ self.navigationController?.pushViewController(vc, animated: true)
2841
3241
  }
2842
3242
  }
2843
3243
  }
@@ -2864,27 +3264,79 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
2864
3264
  // Validation checks
2865
3265
  if cardNumber.isEmpty {
2866
3266
  self.showAlert(title: "Missing Information", message: "Card Number is required.")
3267
+ return
2867
3268
  } else if sanitizedCardNumber.count != 16 {
2868
3269
  self.showAlert(title: "Invalid Card Number", message: "Card Number must be 16 digits.")
3270
+ return
2869
3271
  } else if expiryDate.isEmpty {
2870
3272
  self.showAlert(title: "Missing Information", message: "Card Expiry Date is required.")
2871
- } else if !expiryDate.matches(expiryDateRegex) {
3273
+ return
3274
+ }
3275
+
3276
+ else if !expiryDate.matches(expiryDateRegex) {
2872
3277
  self.showAlert(title: "Invalid Expiry Date", message: "Expiry Date must be in format MM/yyyy (e.g., 02/2026).")
2873
- } else if cvv.isEmpty {
2874
- self.showAlert(title: "Missing Information", message: "Card CVV Number is required.")
2875
- } else if cvv.count < 3 {
2876
- self.showAlert(title: "Invalid CVV", message: "CVV must be at least 3 digits.")
2877
- } else if cardName.isEmpty {
2878
- self.showAlert(title: "Missing Information", message: "Card Name is required.")
2879
- } else if !cardName.matches(nameRegex) {
2880
- self.showAlert(title: "Invalid Name", message: "Name must contain only letters and spaces.")
3278
+ return
2881
3279
  }
2882
3280
  else {
2883
- // Navigate to EmailVerificationVC if isSavedForFuture is true, else call paymentIntentApi
2884
- if isSavedForFuture {
2885
- navigateCardDataToEmailVerificationVC()
3281
+ // Check if expiry date is past tomorrow
3282
+ let dateFormatter = DateFormatter()
3283
+ dateFormatter.dateFormat = "MM/yyyy"
3284
+ dateFormatter.timeZone = TimeZone.current
3285
+ if let expDate = dateFormatter.date(from: expiryDate) {
3286
+ let calendar = Calendar.current
3287
+ // Get start of the month following expiry
3288
+ let expiryMonthStart = calendar.date(from: calendar.dateComponents([.year, .month], from: expDate))!
3289
+ let endOfMonth = calendar.date(byAdding: DateComponents(month: 1, day: -1), to: expiryMonthStart)!
3290
+
3291
+ let tomorrow = calendar.date(byAdding: .day, value: 1, to: Date())!
3292
+ if endOfMonth < tomorrow {
3293
+ self.showAlert(title: "Expired Card", message: "Card is expired or please enter a valid expiry date.")
3294
+ return
3295
+ }
2886
3296
  } else {
2887
- paymentIntentApi()
3297
+ self.showAlert(title: "Invalid Expiry Date", message: "Unable to parse expiry date.")
3298
+ return
3299
+ }
3300
+ }
3301
+
3302
+ if cvv.isEmpty {
3303
+ self.showAlert(title: "Missing Information", message: "Card CVV Number is required.")
3304
+ return
3305
+ } else if cvv.count < 3 {
3306
+ self.showAlert(title: "Invalid CVV", message: "CVV must be at least 3 digits.")
3307
+ return
3308
+ } else if cardName.isEmpty {
3309
+ self.showAlert(title: "Missing Information", message: "Card Name is required.")
3310
+ return
3311
+ } else if !cardName.matches(nameRegex) {
3312
+ self.showAlert(title: "Invalid Name", message: "Name must contain only letters and spaces.")
3313
+ return
3314
+ }
3315
+
3316
+ // Recurring Plan + Date Validation (only if is_recurring is true)
3317
+ if let req = self.request, req.is_recurring == true {
3318
+ if self.txtFieldChosePlanCard.text.isEmpty {
3319
+ self.showAlert(title: "Missing Information", message: "Please choose your plan.")
3320
+ return
3321
+ }
3322
+
3323
+ if let recurringType = req.recurringStartDateType, recurringType == .custom {
3324
+ if self.txtFieldStartDateCard.text.isEmpty {
3325
+ self.showAlert(title: "Missing Information", message: "Please select start date.")
3326
+ return
3327
+ }
3328
+ }
3329
+ }
3330
+
3331
+ // Navigate to EmailVerificationVC if isSavedForFuture is true, else call paymentIntentApi
3332
+ if isSavedForFuture {
3333
+ navigateCardDataToEmailVerificationVC()
3334
+ } else {
3335
+ if request.enable3DS == true {
3336
+ threeDSecurePaymentApi()
3337
+ }
3338
+ else {
3339
+ paymentIntentApi()
2888
3340
  }
2889
3341
  }
2890
3342
  }
@@ -2892,36 +3344,57 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
2892
3344
  // Bank Case
2893
3345
  if txtFieldAccountName.text?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == true {
2894
3346
  showAlert(title: "Missing Information", message: "Bank account name is required.")
3347
+ return
2895
3348
  }
2896
3349
  else if txtFieldRoutingNumber.text?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == true {
2897
3350
  showAlert(title: "Missing Information", message: "Routing number is required.")
3351
+ return
2898
3352
  }
2899
3353
  else if let routingNumber = self.txtFieldRoutingNumber.text?.replacingOccurrences(of: " ", with: ""), routingNumber.count != 9 || !CharacterSet.decimalDigits.isSuperset(of: CharacterSet(charactersIn: routingNumber)) {
2900
3354
  self.showAlert(title: "Invalid Routing Number", message: "Routing number is not correct. It must be exactly 9 digits.")
3355
+ return
2901
3356
  }
2902
3357
  else if txtFieldAccountType.text?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == true {
2903
3358
  showAlert(title: "Missing Information", message: "Bank account type is required.")
3359
+ return
2904
3360
  }
2905
3361
  else if txtFieldAccountNumber.text?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == true {
2906
3362
  showAlert(title: "Missing Information", message: "Bank account number is required.")
3363
+ return
2907
3364
  }
2908
3365
  else if self.txtFieldConfirmBankAccount.text?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == true {
2909
3366
  self.showAlert(title: "Missing Information", message: "Please confirm your bank account number.")
3367
+ return
2910
3368
  }
2911
3369
  else if self.txtFieldAccountNumber.text?.trimmingCharacters(in: .whitespacesAndNewlines) != self.txtFieldConfirmBankAccount.text?.trimmingCharacters(in: .whitespacesAndNewlines) {
2912
3370
  self.showAlert(title: "Account Mismatch", message: "Bank account number and confirmation do not match.")
3371
+ return
2913
3372
  }
2914
3373
  else if !agreeTermsAndCondtition {
2915
3374
  showAlert(title: "Terms and Conditions", message: "Please agree to the terms and conditions")
2916
3375
  return
2917
3376
  }
2918
- else {
2919
- if isSavedForFuture {
2920
- navigateBankDataToEmailVerificationVC()
2921
- } else {
2922
- accountChargeApi()
3377
+
3378
+ // Recurring Plan + Date Validation (only if is_recurring is true)
3379
+ if let req = self.request, req.is_recurring == true {
3380
+ if self.txtFieldSelectPlanViewBankFields.text.isEmpty {
3381
+ self.showAlert(title: "Missing Information", message: "Please choose your plan.")
3382
+ return
3383
+ }
3384
+
3385
+ if let recurringType = req.recurringStartDateType, recurringType == .custom {
3386
+ if self.txtFieldSelectDateViewBankFields.text.isEmpty {
3387
+ self.showAlert(title: "Missing Information", message: "Please select start date.")
3388
+ return
3389
+ }
2923
3390
  }
2924
3391
  }
3392
+
3393
+ if isSavedForFuture {
3394
+ navigateBankDataToEmailVerificationVC()
3395
+ } else {
3396
+ accountChargeApi()
3397
+ }
2925
3398
  }
2926
3399
  }
2927
3400
  }
@@ -2936,6 +3409,9 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
2936
3409
  emailVerificationVC.selectedPaymentMethod = self.selectedPaymentMethod
2937
3410
  emailVerificationVC.isSavedForFuture = isSavedForFuture
2938
3411
  emailVerificationVC.easyPayDelegate = self.easyPayDelegate // Pass delegate if needed
3412
+ emailVerificationVC.request = self.request
3413
+ emailVerificationVC.chosenPlan = self.txtFieldChosePlanCard.text
3414
+ emailVerificationVC.startDate = self.txtFieldStartDateCard.text
2939
3415
  self.navigationController?.pushViewController(emailVerificationVC, animated: true)
2940
3416
  }
2941
3417
  }
@@ -2949,6 +3425,9 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
2949
3425
  emailVerificationVC.selectedPaymentMethod = self.selectedPaymentMethod
2950
3426
  emailVerificationVC.isSavedForFuture = isSavedForFuture
2951
3427
  emailVerificationVC.easyPayDelegate = self.easyPayDelegate // Pass delegate if needed
3428
+ emailVerificationVC.chosenPlan = self.txtFieldSelectPlanViewBankFields.text
3429
+ emailVerificationVC.startDate = self.txtFieldSelectDateViewBankFields.text
3430
+ emailVerificationVC.request = self.request
2952
3431
  self.navigationController?.pushViewController(emailVerificationVC, animated: true)
2953
3432
  }
2954
3433
  }
@@ -3010,6 +3489,23 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
3010
3489
  self.viewCrypto.isHidden = true
3011
3490
  self.viewSingleSavedAccount.isHidden = true
3012
3491
  self.viewChangedAccount.isHidden = true
3492
+
3493
+ if request?.authenticatedACH == true {
3494
+ self.viewBankFields.isHidden = true
3495
+ self.viewBtnShowSavedCards.isHidden = true
3496
+ self.btnNext.isHidden = true
3497
+ self.viewTermsAndConditions.isHidden = true
3498
+ self.btnNextHeight.constant = 0
3499
+ self.btnNextTopCon.constant = 0
3500
+
3501
+ self.viewBtnShowSavedCards.isHidden = false
3502
+ self.lblBtnShowSaveCard.text = "Show Saved Accounts"
3503
+ self.viewBtnShowSavedCardHeight.constant = 50
3504
+ self.viewBtnShowSavedCardTopCon.constant = 20
3505
+ grailPayBankLinkView.isHidden = false
3506
+ viewAddNewGrailPayAccount.isHidden = true
3507
+ }
3508
+
3013
3509
  }
3014
3510
  }
3015
3511
 
@@ -3052,9 +3548,13 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
3052
3548
 
3053
3549
  if isSelectForPayBank {
3054
3550
  // Show or hide recurring plan fields based on request.enableRecurring
3055
- if request?.enableRecurring == true {
3551
+ if request?.is_recurring == true {
3056
3552
  txtFieldSelectPlanSingleSavedBankView.isHidden = false
3057
- txtFieldSelectDateSingleSavedBankView.isHidden = false
3553
+ if request.recurringStartDateType == .custom {
3554
+ txtFieldSelectDateSingleSavedBankView.isHidden = false
3555
+ } else {
3556
+ txtFieldSelectDateSingleSavedBankView.isHidden = true
3557
+ }
3058
3558
  } else {
3059
3559
  txtFieldSelectPlanSingleSavedBankView.isHidden = true
3060
3560
  txtFieldSelectDateSingleSavedBankView.isHidden = true
@@ -3083,7 +3583,31 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
3083
3583
  }
3084
3584
 
3085
3585
  @IBAction func actionBtnPayNowSingleAccountView(_ sender: UIButton) {
3586
+ guard let selectedIndex = selectedBankIndex else {
3587
+ showAlert(message: "Please select a bank account")
3588
+ return
3589
+ }
3590
+
3591
+ let selectedBank = bankAccounts[selectedIndex]
3592
+ // Log for debugging
3593
+ print("Pay Now tapped with account: ****\(selectedBank.account_number_last_4 ?? "")")
3594
+
3086
3595
  if request.authenticatedACH == true {
3596
+ // Recurring Plan + Date Validation (only if is_recurring is true)
3597
+ if let req = self.request, req.is_recurring == true {
3598
+ if txtFieldSelectPlanSingleSavedBankView.text.isEmpty {
3599
+ self.showAlert(title: "Missing Information", message: "Please choose your plan.")
3600
+ return
3601
+ }
3602
+
3603
+ if let recurringType = req.recurringStartDateType, recurringType == .custom {
3604
+ if txtFieldSelectDateSingleSavedBankView.text.isEmpty {
3605
+ self.showAlert(title: "Missing Information", message: "Please select a plan start date.")
3606
+ return
3607
+ }
3608
+ }
3609
+ }
3610
+
3087
3611
  // Check if the terms and conditions are agreed
3088
3612
  if !agreeTermsAndCondtition {
3089
3613
  showAlert(title: "Terms and Conditions", message: "Please agree to the terms and conditions")
@@ -3096,12 +3620,26 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
3096
3620
  let json = try? JSONSerialization.jsonObject(with: billingInfoData, options: []),
3097
3621
  let jsonDict = json as? [String: Any], !jsonDict.isEmpty {
3098
3622
 
3623
+ // Recurring Plan + Date Validation (only if is_recurring is true)
3624
+ if let req = self.request, req.is_recurring == true {
3625
+ if txtFieldSelectPlanSingleSavedBankView.text.isEmpty {
3626
+ self.showAlert(title: "Missing Information", message: "Please choose your plan.")
3627
+ return
3628
+ }
3629
+
3630
+ if let recurringType = req.recurringStartDateType, recurringType == .custom {
3631
+ if txtFieldSelectDateSingleSavedBankView.text.isEmpty {
3632
+ self.showAlert(title: "Missing Information", message: "Please select a plan start date.")
3633
+ return
3634
+ }
3635
+ }
3636
+ }
3637
+
3099
3638
  // Check if the terms and conditions are agreed
3100
3639
  if !agreeTermsAndCondtition {
3101
3640
  showAlert(title: "Terms and Conditions", message: "Please agree to the terms and conditions")
3102
3641
  return
3103
3642
  }
3104
-
3105
3643
  // Proceed with the Pay Now action
3106
3644
  let vc = easymerchantsdk.instantiateViewController(withIdentifier: "BillingInfoVC") as! BillingInfoVC
3107
3645
  // Pass the customer_id and account_id to BillingInfoVC
@@ -3110,14 +3648,32 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
3110
3648
  vc.billingInfoData = jsonDict
3111
3649
  vc.isFrom = "SavedBank"
3112
3650
  vc.selectedPaymentMethod = "Bank"
3651
+ vc.chosenPlan = self.txtFieldSelectPlanSingleSavedBankView.text
3652
+ vc.startDate = self.txtFieldSelectDateSingleSavedBankView.text
3653
+ vc.request = self.request
3113
3654
  self.navigationController?.pushViewController(vc, animated: true)
3114
3655
  }
3115
3656
  else {
3116
3657
  //If Billing info is nil or empty
3658
+ if let req = self.request, req.is_recurring == true {
3659
+ if txtFieldSelectPlanSingleSavedBankView.text.isEmpty {
3660
+ self.showAlert(title: "Missing Information", message: "Please choose your plan.")
3661
+ return
3662
+ }
3663
+
3664
+ if let recurringType = req.recurringStartDateType, recurringType == .custom {
3665
+ if txtFieldSelectDateSingleSavedBankView.text.isEmpty {
3666
+ self.showAlert(title: "Missing Information", message: "Please select a plan start date.")
3667
+ return
3668
+ }
3669
+ }
3670
+ }
3671
+
3117
3672
  if !agreeTermsAndCondtition {
3118
3673
  showAlert(title: "Terms and Conditions", message: "Please agree to the terms and conditions")
3119
3674
  return
3120
3675
  }
3676
+
3121
3677
  accountChargeSavedBankAccountApi()
3122
3678
  }
3123
3679
  }
@@ -3175,6 +3731,21 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
3175
3731
  return
3176
3732
  }
3177
3733
 
3734
+ // Recurring Plan + Date Validation (only if is_recurring is true)
3735
+ if let req = self.request, req.is_recurring == true {
3736
+ if txtFieldSelectPlanNewAccountView.text.isEmpty {
3737
+ self.showAlert(title: "Missing Information", message: "Please choose your plan.")
3738
+ return
3739
+ }
3740
+
3741
+ if let recurringType = req.recurringStartDateType, recurringType == .custom {
3742
+ if txtFieldSelectDateNewAccountView.text.isEmpty {
3743
+ self.showAlert(title: "Missing Information", message: "Please select a plan start date.")
3744
+ return
3745
+ }
3746
+ }
3747
+ }
3748
+
3178
3749
  // Determine the vc.isFrom value based on isSavedNewAccount
3179
3750
  let isSavedNewAccount = isSavedNewAccount
3180
3751
  let isFromValue = isSavedNewAccount ? "AddNewAccountWithSave" : "AddNewAccountWithoutSave"
@@ -3190,10 +3761,56 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
3190
3761
  vc.isFrom = isFromValue
3191
3762
  vc.isSavedNewAccount = isSavedNewAccount
3192
3763
  vc.delegate = self
3764
+ vc.chosenPlan = self.txtFieldSelectPlanNewAccountView.text
3765
+ vc.startDate = self.txtFieldSelectDateNewAccountView.text
3766
+ vc.request = self.request
3193
3767
  self.navigationController?.pushViewController(vc, animated: true)
3194
3768
  }
3195
3769
  else {
3196
3770
  //If Billing info is nil or empty
3771
+
3772
+ // Retrieve and trim text field values (removing leading and trailing whitespace)
3773
+ let accountName = txtFieldAccountNameNewAccountView.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
3774
+ let routingNumber = txtFieldRoutingNumberNewAccountView.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
3775
+ let accountType = txtFieldAccountTypeNewAccountView.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
3776
+ let accountNumber = txtFieldAccountNumberNewAccountView.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
3777
+ let confirmAccountNumber = txtFieldConfirmAccountNewAccountView.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
3778
+
3779
+ // Check if any field is empty
3780
+ if accountName.isEmpty || routingNumber.isEmpty || accountType.isEmpty || accountNumber.isEmpty || confirmAccountNumber.isEmpty {
3781
+ let alert = UIAlertController(title: "Missing Information", message: "Please fill in all bank details.", preferredStyle: .alert)
3782
+ alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
3783
+ self.present(alert, animated: true, completion: nil)
3784
+ return
3785
+ }
3786
+
3787
+ // Check if routing number is exactly 9 digits and numeric
3788
+ if routingNumber.count != 9 || !CharacterSet.decimalDigits.isSuperset(of: CharacterSet(charactersIn: routingNumber)) {
3789
+ self.showAlert(title: "Invalid Routing Number", message: "Routing number is not correct. It must be exactly 9 digits.")
3790
+ return
3791
+ }
3792
+
3793
+ // Check if account number and confirmation match
3794
+ if accountNumber != confirmAccountNumber {
3795
+ self.showAlert(title: "Account Mismatch", message: "Bank account number and confirmation do not match.")
3796
+ return
3797
+ }
3798
+
3799
+ // Recurring Plan + Date Validation (only if is_recurring is true)
3800
+ if let req = self.request, req.is_recurring == true {
3801
+ if txtFieldSelectPlanNewAccountView.text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
3802
+ self.showAlert(title: "Missing Information", message: "Please choose your plan.")
3803
+ return
3804
+ }
3805
+
3806
+ if let recurringType = req.recurringStartDateType, recurringType == .custom {
3807
+ if txtFieldSelectDateNewAccountView.text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
3808
+ self.showAlert(title: "Missing Information", message: "Please select a plan start date.")
3809
+ return
3810
+ }
3811
+ }
3812
+ }
3813
+
3197
3814
  let isSavedNewAccount = isSavedNewAccount
3198
3815
  // If Billing info is nil or empty, call the appropriate API
3199
3816
  if isSavedNewAccount {
@@ -3614,38 +4231,40 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
3614
4231
  // MARK: - GET Show Cards API
3615
4232
  func getShowCardsApi() {
3616
4233
  showLoadingIndicator()
3617
-
4234
+
3618
4235
  let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.getCards.path()
3619
-
4236
+
3620
4237
  guard let serviceURL = URL(string: fullURL) else {
3621
4238
  print("❌ Invalid URL: \(fullURL)")
3622
4239
  hideLoadingIndicator()
3623
4240
  return
3624
4241
  }
3625
-
3626
- var request = URLRequest(url: serviceURL)
3627
- request.httpMethod = "GET"
3628
- request.addValue("application/json", forHTTPHeaderField: "Content-Type")
3629
-
4242
+
4243
+ var uRLRequest = URLRequest(url: serviceURL)
4244
+ uRLRequest.httpMethod = "GET"
4245
+ uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
4246
+
3630
4247
  let token = UserStoreSingleton.shared.customerToken
3631
4248
  print("🔑 Setting customerToken header: \(token ?? "None")")
3632
- request.addValue(token ?? "", forHTTPHeaderField: "Customer-Token")
3633
-
4249
+ uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Customer-Token")
4250
+
3634
4251
  // Add API headers
3635
4252
  if let apiKey = EnvironmentConfig.apiKey,
3636
4253
  let apiSecret = EnvironmentConfig.apiSecret {
3637
- request.addValue(apiKey, forHTTPHeaderField: "x-api-key")
3638
- request.addValue(apiSecret, forHTTPHeaderField: "x-api-secret")
4254
+ uRLRequest.addValue(apiKey, forHTTPHeaderField: "x-api-key")
4255
+ uRLRequest.addValue(apiSecret, forHTTPHeaderField: "x-api-secret")
3639
4256
  }
3640
-
4257
+
3641
4258
  let session = URLSession.shared
3642
- let task = session.dataTask(with: request) { [weak self] (data, response, error) in
4259
+ let task = session.dataTask(with: uRLRequest) { [weak self] (data, response, error) in
3643
4260
  guard let self = self else { return }
3644
-
3645
- DispatchQueue.main.async {
3646
- self.hideLoadingIndicator()
4261
+
4262
+ defer {
4263
+ DispatchQueue.main.async {
4264
+ self.hideLoadingIndicator()
4265
+ }
3647
4266
  }
3648
-
4267
+
3649
4268
  if let error = error {
3650
4269
  print("❌ Error in getShowCardsApi: \(error.localizedDescription)")
3651
4270
  if let httpResponse = response as? HTTPURLResponse {
@@ -3653,23 +4272,23 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
3653
4272
  }
3654
4273
  return
3655
4274
  }
3656
-
4275
+
3657
4276
  guard let data = data else {
3658
4277
  print("❌ No data received in getShowCardsApi.")
3659
4278
  return
3660
4279
  }
3661
-
4280
+
3662
4281
  do {
3663
4282
  // Print raw JSON string for inspection
3664
4283
  if let rawJSON = String(data: data, encoding: .utf8) {
3665
4284
  print("📦 Raw JSON Response:\n\(rawJSON)")
3666
4285
  }
3667
-
4286
+
3668
4287
  if let jsonResponse = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
3669
4288
  let cards = jsonResponse["Cards"] as? [[String: Any]] {
3670
-
4289
+
3671
4290
  print("✅ Parsed JSON Response:\n\(jsonResponse)")
3672
-
4291
+
3673
4292
  // Map JSON response to CardModel array
3674
4293
  self.savedCards = cards.compactMap { cardDict in
3675
4294
  return CardModel(
@@ -3697,25 +4316,32 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
3697
4316
  --------------------------
3698
4317
  """)
3699
4318
  }
3700
-
4319
+
3701
4320
  DispatchQueue.main.async {
3702
4321
  if let firstCard = self.savedCards.first {
3703
4322
  self.lblCardNumberSigleSavedCard.text = "****\(firstCard.ccLast4)"
3704
4323
  self.lblExpireDateSingelSavedCard.text = "Expiry: \(firstCard.ccValidThru)"
4324
+
4325
+ self.selectedCardIndex = 0
4326
+ self.selectedCard = self.savedCards[0]
4327
+ self.updateSingleCardView(for: 0)
4328
+
3705
4329
  }
3706
-
4330
+
3707
4331
  self.tblViewSavedCardsList.reloadData()
3708
-
4332
+
3709
4333
  if self.isSelectForPay {
3710
4334
  self.viewTxtFieldCVVSingleCard.isHidden = false
3711
4335
  self.btnPayNowSingleCard.isHidden = false
3712
4336
  self.btnNext.isHidden = true
3713
4337
  self.btnNextHeight.constant = 0
3714
4338
  self.btnNextTopCon.constant = 0
3715
-
3716
- if self.request.enableRecurring == true {
4339
+
4340
+ if self.request.is_recurring == true {
3717
4341
  self.txtFieldSelectPlanSingleSavedCard.isHidden = false
3718
- self.txtFieldSelectDateSingleSavedCard.isHidden = false
4342
+ if self.request.recurringStartDateType == .custom {
4343
+ self.txtFieldSelectDateSingleSavedCard.isHidden = false
4344
+ }
3719
4345
  } else {
3720
4346
  self.txtFieldSelectPlanSingleSavedCard.isHidden = true
3721
4347
  self.txtFieldSelectDateSingleSavedCard.isHidden = true
@@ -3744,7 +4370,10 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
3744
4370
  func paymentIntentApi() {
3745
4371
  showLoadingIndicator()
3746
4372
 
3747
- let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.creditCharges.path()
4373
+ ///Old Using URL Endpoints
4374
+ // let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.creditCharges.path()
4375
+
4376
+ let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.charges.path()
3748
4377
 
3749
4378
  guard let serviceURL = URL(string: fullURL) else {
3750
4379
  print("Invalid URL")
@@ -3752,37 +4381,62 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
3752
4381
  return
3753
4382
  }
3754
4383
 
3755
- var request = URLRequest(url: serviceURL)
3756
- request.httpMethod = "POST"
3757
- request.addValue("application/json", forHTTPHeaderField: "Content-Type")
4384
+ var uRLRequest = URLRequest(url: serviceURL)
4385
+ uRLRequest.httpMethod = "POST"
4386
+ uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
3758
4387
 
3759
4388
  let token = UserStoreSingleton.shared.clientToken
3760
4389
  print("Setting clientToken header: \(token ?? "None")")
3761
- request.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
4390
+ uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
3762
4391
 
3763
4392
  // Add API headers
3764
4393
  if let apiKey = EnvironmentConfig.apiKey,
3765
4394
  let apiSecret = EnvironmentConfig.apiSecret {
3766
- request.addValue(apiKey, forHTTPHeaderField: "x-api-key")
3767
- request.addValue(apiSecret, forHTTPHeaderField: "x-api-secret")
4395
+ uRLRequest.addValue(apiKey, forHTTPHeaderField: "x-api-key")
4396
+ uRLRequest.addValue(apiSecret, forHTTPHeaderField: "x-api-secret")
3768
4397
  }
3769
4398
 
3770
- let params: [String: Any] = [
4399
+ var params: [String: Any] = [
3771
4400
  "name": cardNameTextField.text,
3772
- "email": "test@gmail.com",
4401
+ "email": UserStoreSingleton.shared.merchantEmail ?? "",
3773
4402
  "card_number": cardNumberTextField.text.replacingOccurrences(of: " ", with: ""),
3774
4403
  "cardholder_name": cardNameTextField.text,
3775
4404
  "exp_month": cardExpiryTextField.text.components(separatedBy: "/").first ?? "",
3776
4405
  "exp_year": cardExpiryTextField.text.components(separatedBy: "/").last ?? "",
3777
4406
  "cvc": cardCvvTextField.text,
3778
- "description": "TestDescription",
3779
- "currency": "usd",
3780
- "payment_method": "card"
4407
+ "description": "payment checkout",
4408
+ "currency": "usd"
4409
+ // "payment_method": "card"
3781
4410
  ]
3782
4411
 
4412
+ // Add these if recurring is enabled
4413
+ if let req = request, req.is_recurring == true {
4414
+ if let recurringType = req.recurringStartDateType, recurringType == .custom {
4415
+ // Only send start_date if type is .custom and field is not empty
4416
+ if let startDateText = txtFieldStartDateCard?.text, !startDateText.isEmpty {
4417
+ let inputFormatter = DateFormatter()
4418
+ inputFormatter.dateFormat = "dd/MM/yyyy"
4419
+
4420
+ let outputFormatter = DateFormatter()
4421
+ outputFormatter.dateFormat = "MM/dd/yyyy"
4422
+
4423
+ if let date = inputFormatter.date(from: startDateText) {
4424
+ let apiFormattedDate = outputFormatter.string(from: date)
4425
+ params["start_date"] = apiFormattedDate
4426
+ } else {
4427
+ print("Invalid date format in startDateText")
4428
+ }
4429
+ }
4430
+ }
4431
+
4432
+ params["interval"] = txtFieldChosePlanCard.text.lowercased()
4433
+ }
4434
+
4435
+ print(params)
4436
+
3783
4437
  do {
3784
4438
  let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
3785
- request.httpBody = jsonData
4439
+ uRLRequest.httpBody = jsonData
3786
4440
  if let jsonString = String(data: jsonData, encoding: .utf8) {
3787
4441
  print("JSON Payload: \(jsonString)")
3788
4442
  }
@@ -3793,7 +4447,7 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
3793
4447
  }
3794
4448
 
3795
4449
  let session = URLSession.shared
3796
- let task = session.dataTask(with: request) { (serviceData, serviceResponse, error) in
4450
+ let task = session.dataTask(with: uRLRequest) { (serviceData, serviceResponse, error) in
3797
4451
 
3798
4452
  DispatchQueue.main.async {
3799
4453
  self.hideLoadingIndicator() // Stop loader when response is received
@@ -3854,54 +4508,89 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
3854
4508
  // MARK: - Credit Card Charge API (Saved Cards, No Billing Info, Logged In)
3855
4509
  func paymentIntentFromShowSavedCardApi() {
3856
4510
  showLoadingIndicator()
3857
-
3858
- let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.creditCharges.path()
3859
-
4511
+
4512
+ ///Old Using URL Endpoints
4513
+ // let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.creditCharges.path()
4514
+
4515
+ let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.charges.path()
4516
+
3860
4517
  guard let serviceURL = URL(string: fullURL) else {
3861
4518
  print("Invalid URL")
3862
4519
  hideLoadingIndicator()
3863
4520
  return
3864
4521
  }
3865
-
3866
- var request = URLRequest(url: serviceURL)
3867
- request.httpMethod = "POST"
3868
- request.addValue("application/json", forHTTPHeaderField: "Content-Type")
3869
-
4522
+
4523
+ var uRLRequest = URLRequest(url: serviceURL)
4524
+ uRLRequest.httpMethod = "POST"
4525
+ uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
4526
+
3870
4527
  // Add client token
3871
4528
  let token = UserStoreSingleton.shared.clientToken
3872
4529
  print("Setting clientToken header: \(token ?? "None")")
3873
- request.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
3874
-
4530
+ uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
4531
+
3875
4532
  // Add API headers
3876
4533
  if let apiKey = EnvironmentConfig.apiKey,
3877
4534
  let apiSecret = EnvironmentConfig.apiSecret {
3878
- request.addValue(apiKey, forHTTPHeaderField: "x-api-key")
3879
- request.addValue(apiSecret, forHTTPHeaderField: "x-api-secret")
4535
+ uRLRequest.addValue(apiKey, forHTTPHeaderField: "x-api-key")
4536
+ uRLRequest.addValue(apiSecret, forHTTPHeaderField: "x-api-secret")
3880
4537
  }
3881
-
3882
- // Prepare parameters
3883
- let email = UserStoreSingleton.shared.verificationEmail ?? ""
3884
- let namePart = email.split(separator: "@").first.map(String.init) ?? ""
3885
-
3886
- let params: [String: Any] = [
3887
- "name": namePart,
3888
- "email": email,
3889
- "description": "TestDescription",
4538
+
4539
+ ///OLD Params
4540
+ // // Prepare parameters
4541
+ // let email = UserStoreSingleton.shared.verificationEmail ?? ""
4542
+ // let namePart = email.split(separator: "@").first.map(String.init) ?? ""
4543
+ //
4544
+ // var params: [String: Any] = [
4545
+ // "name": namePart,
4546
+ // "email": email,
4547
+ // "description": "TestDescription",
4548
+ // "currency": "usd",
4549
+ // "payment_method": "card",
4550
+ // "save_card": 0,
4551
+ // "customer": selectedCard?.customerId ?? "",
4552
+ // "card_id": selectedCard?.cardId ?? "",
4553
+ // "cvc": txtFieldCVVSingleSavedCard.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "",
4554
+ // "customer_id": selectedCard?.customerId ?? ""
4555
+ // ]
4556
+
4557
+ var params: [String: Any] = [
4558
+ "description": "payment checkout",
3890
4559
  "currency": "usd",
3891
- "payment_method": "card",
3892
- "save_card": 0,
3893
- "customer": selectedCard?.customerId ?? "",
3894
4560
  "card_id": selectedCard?.cardId ?? "",
3895
4561
  "cvc": txtFieldCVVSingleSavedCard.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "",
3896
4562
  "customer_id": selectedCard?.customerId ?? ""
3897
4563
  ]
3898
-
4564
+
4565
+ // Add these if recurring is enabled
4566
+ if let req = request, req.is_recurring == true {
4567
+ if let recurringType = req.recurringStartDateType, recurringType == .custom {
4568
+ // Only send start_date if type is .custom and field is not empty
4569
+ if let startDateText = txtFieldSelectDateSingleSavedCard?.text, !startDateText.isEmpty {
4570
+ let inputFormatter = DateFormatter()
4571
+ inputFormatter.dateFormat = "dd/MM/yyyy"
4572
+
4573
+ let outputFormatter = DateFormatter()
4574
+ outputFormatter.dateFormat = "MM/dd/yyyy"
4575
+
4576
+ if let date = inputFormatter.date(from: startDateText) {
4577
+ let apiFormattedDate = outputFormatter.string(from: date)
4578
+ params["start_date"] = apiFormattedDate
4579
+ } else {
4580
+ print("Invalid date format in startDateText")
4581
+ }
4582
+ }
4583
+ }
4584
+
4585
+ params["interval"] = txtFieldSelectPlanSingleSavedCard.text.lowercased()
4586
+ }
4587
+
3899
4588
  print("Request Params: \(params)")
3900
-
4589
+
3901
4590
  do {
3902
4591
  let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
3903
- request.httpBody = jsonData
3904
-
4592
+ uRLRequest.httpBody = jsonData
4593
+
3905
4594
  if let jsonString = String(data: jsonData, encoding: .utf8) {
3906
4595
  print("JSON Payload: \(jsonString)")
3907
4596
  }
@@ -3910,29 +4599,29 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
3910
4599
  hideLoadingIndicator()
3911
4600
  return
3912
4601
  }
3913
-
4602
+
3914
4603
  // API Call
3915
- let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
4604
+ let task = URLSession.shared.dataTask(with: uRLRequest) { (data, response, error) in
3916
4605
  DispatchQueue.main.async {
3917
4606
  self.hideLoadingIndicator()
3918
4607
  }
3919
-
4608
+
3920
4609
  if let error = error {
3921
4610
  print("Error: \(error.localizedDescription)")
3922
4611
  return
3923
4612
  }
3924
-
4613
+
3925
4614
  guard let httpResponse = response as? HTTPURLResponse else {
3926
4615
  print("Invalid response")
3927
4616
  return
3928
4617
  }
3929
-
4618
+
3930
4619
  if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
3931
4620
  if let data = data {
3932
4621
  do {
3933
4622
  if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
3934
4623
  print("Response Data: \(responseObject)")
3935
-
4624
+
3936
4625
  if let status = responseObject["status"] as? Int, status == 0 {
3937
4626
  let errorMessage = responseObject["message"] as? String ?? "Unknown error"
3938
4627
  self.presentPaymentErrorVC(errorMessage: errorMessage)
@@ -3970,31 +4659,6 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
3970
4659
 
3971
4660
  //MARK: - Credit Card Charge Api from Add new cards If Billing info is nil and Logged in.
3972
4661
  func paymentIntentFromAddNewCardApi(customerId: String?) {
3973
- showLoadingIndicator()
3974
-
3975
- let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.creditCharges.path()
3976
-
3977
- guard let serviceURL = URL(string: fullURL) else {
3978
- print("Invalid URL")
3979
- hideLoadingIndicator()
3980
- return
3981
- }
3982
-
3983
- var request = URLRequest(url: serviceURL)
3984
- request.httpMethod = "POST"
3985
- request.addValue("application/json", forHTTPHeaderField: "Content-Type")
3986
-
3987
- let token = UserStoreSingleton.shared.clientToken
3988
- print("Setting clientToken header: \(token ?? "None")")
3989
- request.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
3990
-
3991
- // Add API headers
3992
- if let apiKey = EnvironmentConfig.apiKey,
3993
- let apiSecret = EnvironmentConfig.apiSecret {
3994
- request.addValue(apiKey, forHTTPHeaderField: "x-api-key")
3995
- request.addValue(apiSecret, forHTTPHeaderField: "x-api-secret")
3996
- }
3997
-
3998
4662
  // Get the text fields from the selected cell and trim whitespace
3999
4663
  let nameText = txtFieldNameOnCardNewCardView.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
4000
4664
  let cvvText = txtFieldCVVNewCardView.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
@@ -4044,6 +4708,48 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
4044
4708
  return
4045
4709
  }
4046
4710
 
4711
+ // Recurring Plan + Date Validation (only if is_recurring is true)
4712
+ if let req = self.request, req.is_recurring == true {
4713
+ let planText = txtFieldSelectPlanNewCardView.text.trimmingCharacters(in: .whitespacesAndNewlines)
4714
+ let dateText = txtFieldSelectDateNewCardView.text.trimmingCharacters(in: .whitespacesAndNewlines)
4715
+
4716
+ if planText.isEmpty {
4717
+ self.showAlert(title: "Missing Information", message: "Please choose your plan.")
4718
+ return
4719
+ }
4720
+
4721
+ if let recurringType = req.recurringStartDateType, recurringType == .custom {
4722
+ if dateText.isEmpty {
4723
+ self.showAlert(title: "Missing Information", message: "Please select a plan start date.")
4724
+ return
4725
+ }
4726
+ }
4727
+ }
4728
+
4729
+ showLoadingIndicator()
4730
+
4731
+ let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.creditCharges.path()
4732
+
4733
+ guard let serviceURL = URL(string: fullURL) else {
4734
+ print("Invalid URL")
4735
+ hideLoadingIndicator()
4736
+ return
4737
+ }
4738
+
4739
+ var uRLRequest = URLRequest(url: serviceURL)
4740
+ uRLRequest.httpMethod = "POST"
4741
+ uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
4742
+
4743
+ let token = UserStoreSingleton.shared.clientToken
4744
+ print("Setting clientToken header: \(token ?? "None")")
4745
+ uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
4746
+
4747
+ // Add API headers
4748
+ if let apiKey = EnvironmentConfig.apiKey,
4749
+ let apiSecret = EnvironmentConfig.apiSecret {
4750
+ uRLRequest.addValue(apiKey, forHTTPHeaderField: "x-api-key")
4751
+ uRLRequest.addValue(apiSecret, forHTTPHeaderField: "x-api-secret")
4752
+ }
4047
4753
 
4048
4754
  let emailPrefix = UserStoreSingleton.shared.verificationEmail?.components(separatedBy: "@").first ?? ""
4049
4755
 
@@ -4073,11 +4779,34 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
4073
4779
  params["username"] = emailPrefix
4074
4780
  }
4075
4781
 
4782
+ // Add these if recurring is enabled
4783
+ if let req = request, req.is_recurring == true {
4784
+ if let recurringType = req.recurringStartDateType, recurringType == .custom {
4785
+ // Only send start_date if type is .custom and field is not empty
4786
+ if let startDateText = txtFieldSelectDateNewCardView?.text, !startDateText.isEmpty {
4787
+ let inputFormatter = DateFormatter()
4788
+ inputFormatter.dateFormat = "dd/MM/yyyy"
4789
+
4790
+ let outputFormatter = DateFormatter()
4791
+ outputFormatter.dateFormat = "MM/dd/yyyy"
4792
+
4793
+ if let date = inputFormatter.date(from: startDateText) {
4794
+ let apiFormattedDate = outputFormatter.string(from: date)
4795
+ params["start_date"] = apiFormattedDate
4796
+ } else {
4797
+ print("Invalid date format in startDateText")
4798
+ }
4799
+ }
4800
+ }
4801
+
4802
+ params["interval"] = txtFieldSelectPlanNewCardView.text.lowercased()
4803
+ }
4804
+
4076
4805
  print(params)
4077
4806
 
4078
4807
  do {
4079
4808
  let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
4080
- request.httpBody = jsonData
4809
+ uRLRequest.httpBody = jsonData
4081
4810
  if let jsonString = String(data: jsonData, encoding: .utf8) {
4082
4811
  print("JSON Payload: \(jsonString)")
4083
4812
  }
@@ -4088,7 +4817,7 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
4088
4817
  }
4089
4818
 
4090
4819
  let session = URLSession.shared
4091
- let task = session.dataTask(with: request) { (serviceData, serviceResponse, error) in
4820
+ let task = session.dataTask(with: uRLRequest) { (serviceData, serviceResponse, error) in
4092
4821
 
4093
4822
  DispatchQueue.main.async {
4094
4823
  self.hideLoadingIndicator() // Stop loader when response is received
@@ -4504,9 +5233,13 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
4504
5233
  self.viewTermAndConditionsSingleAccountView.isHidden = false
4505
5234
  self.btnPayNowSingleAccountView.isHidden = false
4506
5235
 
4507
- if self.request.enableRecurring == true {
5236
+ if self.request.is_recurring == true {
4508
5237
  self.txtFieldSelectPlanSingleSavedBankView.isHidden = false
4509
- self.txtFieldSelectDateSingleSavedBankView.isHidden = false
5238
+ if self.request.recurringStartDateType == .custom {
5239
+ self.txtFieldSelectDateSingleSavedBankView.isHidden = false
5240
+ } else {
5241
+ self.txtFieldSelectDateSingleSavedBankView.isHidden = true
5242
+ }
4510
5243
  }
4511
5244
  else {
4512
5245
  self.txtFieldSelectPlanSingleSavedBankView.isHidden = true
@@ -4553,22 +5286,22 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
4553
5286
  return
4554
5287
  }
4555
5288
 
4556
- var request = URLRequest(url: serviceURL)
4557
- request.httpMethod = "POST"
4558
- request.addValue("application/json", forHTTPHeaderField: "Content-Type")
5289
+ var uRLRequest = URLRequest(url: serviceURL)
5290
+ uRLRequest.httpMethod = "POST"
5291
+ uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
4559
5292
 
4560
5293
  let token = UserStoreSingleton.shared.clientToken
4561
5294
  print("Setting clientToken header: \(token ?? "None")")
4562
- request.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
5295
+ uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
4563
5296
 
4564
5297
  // Add API headers
4565
5298
  if let apiKey = EnvironmentConfig.apiKey,
4566
5299
  let apiSecret = EnvironmentConfig.apiSecret {
4567
- request.addValue(apiKey, forHTTPHeaderField: "x-api-key")
4568
- request.addValue(apiSecret, forHTTPHeaderField: "x-api-secret")
5300
+ uRLRequest.addValue(apiKey, forHTTPHeaderField: "x-api-key")
5301
+ uRLRequest.addValue(apiSecret, forHTTPHeaderField: "x-api-secret")
4569
5302
  }
4570
5303
 
4571
- let params: [String: Any] = [
5304
+ var params: [String: Any] = [
4572
5305
  "name": txtFieldAccountName.text ?? "",
4573
5306
  "email": "test@gmail.com",
4574
5307
  "description": "TestDescription",
@@ -4581,9 +5314,32 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
4581
5314
  "levelIndicator": 1,
4582
5315
  ]
4583
5316
 
5317
+ // Add these if recurring is enabled
5318
+ if let req = request, req.is_recurring == true {
5319
+ if let recurringType = req.recurringStartDateType, recurringType == .custom {
5320
+ // Only send start_date if type is .custom and field is not empty
5321
+ if let startDateText = txtFieldSelectDateViewBankFields?.text, !startDateText.isEmpty {
5322
+ let inputFormatter = DateFormatter()
5323
+ inputFormatter.dateFormat = "dd/MM/yyyy"
5324
+
5325
+ let outputFormatter = DateFormatter()
5326
+ outputFormatter.dateFormat = "MM/dd/yyyy"
5327
+
5328
+ if let date = inputFormatter.date(from: startDateText) {
5329
+ let apiFormattedDate = outputFormatter.string(from: date)
5330
+ params["start_date"] = apiFormattedDate
5331
+ } else {
5332
+ print("Invalid date format in startDateText")
5333
+ }
5334
+ }
5335
+ }
5336
+
5337
+ params["interval"] = txtFieldSelectPlanViewBankFields.text.lowercased()
5338
+ }
5339
+
4584
5340
  do {
4585
5341
  let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
4586
- request.httpBody = jsonData
5342
+ uRLRequest.httpBody = jsonData
4587
5343
  if let jsonString = String(data: jsonData, encoding: .utf8) {
4588
5344
  print("JSON Payload: \(jsonString)")
4589
5345
  }
@@ -4594,7 +5350,7 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
4594
5350
  }
4595
5351
 
4596
5352
  let session = URLSession.shared
4597
- let task = session.dataTask(with: request) { (serviceData, serviceResponse, error) in
5353
+ let task = session.dataTask(with: uRLRequest) { (serviceData, serviceResponse, error) in
4598
5354
 
4599
5355
  DispatchQueue.main.async {
4600
5356
  self.hideLoadingIndicator() // Stop loader when response is received
@@ -4665,22 +5421,22 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
4665
5421
  return
4666
5422
  }
4667
5423
 
4668
- var request = URLRequest(url: serviceURL)
4669
- request.httpMethod = "POST"
4670
- request.addValue("application/json", forHTTPHeaderField: "Content-Type")
5424
+ var uRLRequest = URLRequest(url: serviceURL)
5425
+ uRLRequest.httpMethod = "POST"
5426
+ uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
4671
5427
 
4672
5428
  let token = UserStoreSingleton.shared.clientToken
4673
5429
  print("Setting clientToken header: \(token ?? "None")")
4674
- request.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
5430
+ uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
4675
5431
 
4676
5432
  // Add API headers
4677
5433
  if let apiKey = EnvironmentConfig.apiKey,
4678
5434
  let apiSecret = EnvironmentConfig.apiSecret {
4679
- request.addValue(apiKey, forHTTPHeaderField: "x-api-key")
4680
- request.addValue(apiSecret, forHTTPHeaderField: "x-api-secret")
5435
+ uRLRequest.addValue(apiKey, forHTTPHeaderField: "x-api-key")
5436
+ uRLRequest.addValue(apiSecret, forHTTPHeaderField: "x-api-secret")
4681
5437
  }
4682
5438
 
4683
- let params: [String: Any] = [
5439
+ var params: [String: Any] = [
4684
5440
  "account_id": selectedbankAccounts?.account_id ?? "",
4685
5441
  "customer": selectedbankAccounts?.customer_id ?? "",
4686
5442
  "payment_method": "ach",
@@ -4688,11 +5444,34 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
4688
5444
  "currency": "usd",
4689
5445
  ]
4690
5446
 
5447
+ // Add these if recurring is enabled
5448
+ if let req = request, req.is_recurring == true {
5449
+ if let recurringType = req.recurringStartDateType, recurringType == .custom {
5450
+ // Only send start_date if type is .custom and field is not empty
5451
+ if let startDateText = txtFieldSelectDateSingleSavedBankView?.text, !startDateText.isEmpty {
5452
+ let inputFormatter = DateFormatter()
5453
+ inputFormatter.dateFormat = "dd/MM/yyyy"
5454
+
5455
+ let outputFormatter = DateFormatter()
5456
+ outputFormatter.dateFormat = "MM/dd/yyyy"
5457
+
5458
+ if let date = inputFormatter.date(from: startDateText) {
5459
+ let apiFormattedDate = outputFormatter.string(from: date)
5460
+ params["start_date"] = apiFormattedDate
5461
+ } else {
5462
+ print("Invalid date format in startDateText")
5463
+ }
5464
+ }
5465
+ }
5466
+
5467
+ params["interval"] = txtFieldSelectPlanSingleSavedBankView.text.lowercased()
5468
+ }
5469
+
4691
5470
  print(params)
4692
5471
 
4693
5472
  do {
4694
5473
  let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
4695
- request.httpBody = jsonData
5474
+ uRLRequest.httpBody = jsonData
4696
5475
  if let jsonString = String(data: jsonData, encoding: .utf8) {
4697
5476
  print("JSON Payload: \(jsonString)")
4698
5477
  }
@@ -4703,7 +5482,7 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
4703
5482
  }
4704
5483
 
4705
5484
  let session = URLSession.shared
4706
- let task = session.dataTask(with: request) { (serviceData, serviceResponse, error) in
5485
+ let task = session.dataTask(with: uRLRequest) { (serviceData, serviceResponse, error) in
4707
5486
 
4708
5487
  DispatchQueue.main.async {
4709
5488
  self.hideLoadingIndicator() // Stop loader when response is received
@@ -4774,19 +5553,19 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
4774
5553
  return
4775
5554
  }
4776
5555
 
4777
- var request = URLRequest(url: serviceURL)
4778
- request.httpMethod = "POST"
4779
- request.addValue("application/json", forHTTPHeaderField: "Content-Type")
5556
+ var uRLRequest = URLRequest(url: serviceURL)
5557
+ uRLRequest.httpMethod = "POST"
5558
+ uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
4780
5559
 
4781
5560
  let token = UserStoreSingleton.shared.clientToken
4782
5561
  print("Setting clientToken header: \(token ?? "None")")
4783
- request.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
5562
+ uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
4784
5563
 
4785
5564
  // Add API headers
4786
5565
  if let apiKey = EnvironmentConfig.apiKey,
4787
5566
  let apiSecret = EnvironmentConfig.apiSecret {
4788
- request.addValue(apiKey, forHTTPHeaderField: "x-api-key")
4789
- request.addValue(apiSecret, forHTTPHeaderField: "x-api-secret")
5567
+ uRLRequest.addValue(apiKey, forHTTPHeaderField: "x-api-key")
5568
+ uRLRequest.addValue(apiSecret, forHTTPHeaderField: "x-api-secret")
4790
5569
  }
4791
5570
 
4792
5571
  let accountName = txtFieldAccountNameNewAccountView.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
@@ -4794,7 +5573,7 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
4794
5573
  let accountType = txtFieldAccountTypeNewAccountView.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
4795
5574
  let accountNumber = txtFieldAccountNumberNewAccountView.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
4796
5575
 
4797
- let params: [String: Any] = [
5576
+ var params: [String: Any] = [
4798
5577
  "name": accountName,
4799
5578
  "email": UserStoreSingleton.shared.verificationEmail ?? "",
4800
5579
  "description": "Test Description",
@@ -4807,11 +5586,34 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
4807
5586
  "levelIndicator": 1,
4808
5587
  ]
4809
5588
 
5589
+ // Add these if recurring is enabled
5590
+ if let req = request, req.is_recurring == true {
5591
+ if let recurringType = req.recurringStartDateType, recurringType == .custom {
5592
+ // Only send start_date if type is .custom and field is not empty
5593
+ if let startDateText = txtFieldSelectDateNewAccountView?.text, !startDateText.isEmpty {
5594
+ let inputFormatter = DateFormatter()
5595
+ inputFormatter.dateFormat = "dd/MM/yyyy"
5596
+
5597
+ let outputFormatter = DateFormatter()
5598
+ outputFormatter.dateFormat = "MM/dd/yyyy"
5599
+
5600
+ if let date = inputFormatter.date(from: startDateText) {
5601
+ let apiFormattedDate = outputFormatter.string(from: date)
5602
+ params["start_date"] = apiFormattedDate
5603
+ } else {
5604
+ print("Invalid date format in startDateText")
5605
+ }
5606
+ }
5607
+ }
5608
+
5609
+ params["interval"] = txtFieldSelectPlanNewAccountView.text.lowercased()
5610
+ }
5611
+
4810
5612
  print(params)
4811
5613
 
4812
5614
  do {
4813
5615
  let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
4814
- request.httpBody = jsonData
5616
+ uRLRequest.httpBody = jsonData
4815
5617
  if let jsonString = String(data: jsonData, encoding: .utf8) {
4816
5618
  print("JSON Payload: \(jsonString)")
4817
5619
  }
@@ -4822,7 +5624,7 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
4822
5624
  }
4823
5625
 
4824
5626
  let session = URLSession.shared
4825
- let task = session.dataTask(with: request) { (serviceData, serviceResponse, error) in
5627
+ let task = session.dataTask(with: uRLRequest) { (serviceData, serviceResponse, error) in
4826
5628
 
4827
5629
  DispatchQueue.main.async {
4828
5630
  self.hideLoadingIndicator() // Stop loader when response is received
@@ -4893,19 +5695,19 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
4893
5695
  return
4894
5696
  }
4895
5697
 
4896
- var request = URLRequest(url: serviceURL)
4897
- request.httpMethod = "POST"
4898
- request.addValue("application/json", forHTTPHeaderField: "Content-Type")
5698
+ var uRLRequest = URLRequest(url: serviceURL)
5699
+ uRLRequest.httpMethod = "POST"
5700
+ uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
4899
5701
 
4900
5702
  let token = UserStoreSingleton.shared.clientToken
4901
5703
  print("Setting clientToken header: \(token ?? "None")")
4902
- request.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
5704
+ uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
4903
5705
 
4904
5706
  // Add API headers
4905
5707
  if let apiKey = EnvironmentConfig.apiKey,
4906
5708
  let apiSecret = EnvironmentConfig.apiSecret {
4907
- request.addValue(apiKey, forHTTPHeaderField: "x-api-key")
4908
- request.addValue(apiSecret, forHTTPHeaderField: "x-api-secret")
5709
+ uRLRequest.addValue(apiKey, forHTTPHeaderField: "x-api-key")
5710
+ uRLRequest.addValue(apiSecret, forHTTPHeaderField: "x-api-secret")
4909
5711
  }
4910
5712
 
4911
5713
  // Retrieve and trim text field values (removing leading and trailing whitespace)
@@ -4937,11 +5739,34 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
4937
5739
  params["username"] = emailPrefix
4938
5740
  }
4939
5741
 
5742
+ // Add these if recurring is enabled
5743
+ if let req = request, req.is_recurring == true {
5744
+ if let recurringType = req.recurringStartDateType, recurringType == .custom {
5745
+ // Only send start_date if type is .custom and field is not empty
5746
+ if let startDateText = txtFieldSelectDateNewAccountView?.text, !startDateText.isEmpty {
5747
+ let inputFormatter = DateFormatter()
5748
+ inputFormatter.dateFormat = "dd/MM/yyyy"
5749
+
5750
+ let outputFormatter = DateFormatter()
5751
+ outputFormatter.dateFormat = "MM/dd/yyyy"
5752
+
5753
+ if let date = inputFormatter.date(from: startDateText) {
5754
+ let apiFormattedDate = outputFormatter.string(from: date)
5755
+ params["start_date"] = apiFormattedDate
5756
+ } else {
5757
+ print("Invalid date format in startDateText")
5758
+ }
5759
+ }
5760
+ }
5761
+
5762
+ params["interval"] = txtFieldSelectPlanNewAccountView.text.lowercased()
5763
+ }
5764
+
4940
5765
  print(params)
4941
5766
 
4942
5767
  do {
4943
5768
  let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
4944
- request.httpBody = jsonData
5769
+ uRLRequest.httpBody = jsonData
4945
5770
  if let jsonString = String(data: jsonData, encoding: .utf8) {
4946
5771
  print("JSON Payload: \(jsonString)")
4947
5772
  }
@@ -4952,7 +5777,7 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
4952
5777
  }
4953
5778
 
4954
5779
  let session = URLSession.shared
4955
- let task = session.dataTask(with: request) { (serviceData, serviceResponse, error) in
5780
+ let task = session.dataTask(with: uRLRequest) { (serviceData, serviceResponse, error) in
4956
5781
 
4957
5782
  DispatchQueue.main.async {
4958
5783
  self.hideLoadingIndicator() // Stop loader when response is received
@@ -5110,31 +5935,270 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
5110
5935
 
5111
5936
  let token = UserStoreSingleton.shared.clientToken
5112
5937
  print("Setting clientToken header: \(token ?? "None")")
5113
- request.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
5938
+ request.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
5939
+
5940
+ // Add API headers
5941
+ if let apiKey = EnvironmentConfig.apiKey,
5942
+ let apiSecret = EnvironmentConfig.apiSecret {
5943
+ request.addValue(apiKey, forHTTPHeaderField: "x-api-key")
5944
+ request.addValue(apiSecret, forHTTPHeaderField: "x-api-secret")
5945
+ }
5946
+
5947
+ let params: [String: Any] = [
5948
+ "name": UserStoreSingleton.shared.merchantName ?? "",
5949
+ "email": UserStoreSingleton.shared.merchantEmail ?? "",
5950
+ "description": "Payment checkout",
5951
+ "amount": amount ?? "",
5952
+ "currency": "satoshi",
5953
+ "payment_account": UserStoreSingleton.shared.paymentAccount ?? "",
5954
+ "success_url": "https://stage-api.stage-easymerchant.io/webhook/crypto",
5955
+ "callback_url": "https://stage-api.stage-easymerchant.io/webhook/crypto"
5956
+ ]
5957
+
5958
+ print(params)
5959
+
5960
+ do {
5961
+ let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
5962
+ request.httpBody = jsonData
5963
+ if let jsonString = String(data: jsonData, encoding: .utf8) {
5964
+ print("JSON Payload: \(jsonString)")
5965
+ }
5966
+ } catch let error {
5967
+ print("Error creating JSON data: \(error)")
5968
+ hideLoadingIndicator()
5969
+ return
5970
+ }
5971
+
5972
+ let session = URLSession.shared
5973
+ let task = session.dataTask(with: request) { (serviceData, serviceResponse, error) in
5974
+
5975
+ DispatchQueue.main.async {
5976
+ self.hideLoadingIndicator() // Stop loader when response is received
5977
+ }
5978
+
5979
+ if let error = error {
5980
+ print("Error: \(error.localizedDescription)")
5981
+ return
5982
+ }
5983
+
5984
+ guard let httpResponse = serviceResponse as? HTTPURLResponse else {
5985
+ print("Invalid response")
5986
+ return
5987
+ }
5988
+
5989
+ if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
5990
+ if let data = serviceData {
5991
+ do {
5992
+ if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
5993
+ print("Response Data: \(responseObject)")
5994
+
5995
+ // Check if status is 0 and handle the error
5996
+ if let status = responseObject["status"] as? Int, status == 0 {
5997
+ let errorMessage = responseObject["message"] as? String ?? "Unknown error"
5998
+ self.presentPaymentErrorVC(errorMessage: errorMessage)
5999
+ } else {
6000
+ // Extract charge_id
6001
+ if let chargeId = responseObject["charge_id"] as? String {
6002
+ self.chargeId = chargeId
6003
+ print("Charge ID: \(chargeId)")
6004
+ }
6005
+
6006
+ // Extract chain_invoice address and lightning invoice fields
6007
+ if let data = responseObject["data"] as? [String: Any] {
6008
+ self.chainInvoiceAddress = (data["chain_invoice"] as? [String: Any])?["address"] as? String
6009
+ self.lightningURI = (data["lightning_invoice"] as? [String: Any])?["payreq"] as? String
6010
+
6011
+ // Default to OnChain QR code initially
6012
+ DispatchQueue.main.async {
6013
+ if let address = self.chainInvoiceAddress {
6014
+ self.qrImageView.image = self.generateQRCode(from: address)
6015
+ self.lblBTCAddress.text = "BTC Address: \(address)"
6016
+
6017
+ // Extract the amount from the URI
6018
+ if let uri = data["uri"] as? String {
6019
+ if let amountString = self.extractAmount(from: uri) {
6020
+ // Update the label with the extracted amount
6021
+ DispatchQueue.main.async {
6022
+ self.lblAmmoutCrypto.text = "\(amountString) BTC = $\(self.amount ?? 0) USD"
6023
+ self.lblCryptoAmmountViewTryAgain.text = "\(amountString) BTC = $\(self.amount ?? 0) USD"
6024
+ }
6025
+ }
6026
+ }
6027
+ }
6028
+
6029
+ self.viewCryptoQRCode.isHidden = false
6030
+ self.viewCryptoTryAgain.isHidden = true
6031
+ }
6032
+ } else {
6033
+ self.presentPaymentErrorVC(errorMessage: "Invalid response format")
6034
+ }
6035
+ }
6036
+ } else {
6037
+ self.presentPaymentErrorVC(errorMessage: "Invalid JSON format")
6038
+ }
6039
+ } catch let jsonError {
6040
+ self.presentPaymentErrorVC(errorMessage: "Error parsing JSON: \(jsonError)")
6041
+ }
6042
+ } else {
6043
+ self.presentPaymentErrorVC(errorMessage: "No data received")
6044
+ }
6045
+ } else {
6046
+ if let data = serviceData,
6047
+ let responseObj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
6048
+ let message = responseObj["message"] as? String {
6049
+ self.presentPaymentErrorVC(errorMessage: message)
6050
+ } else {
6051
+ self.presentPaymentErrorVC(errorMessage: "HTTP Status Code: \(httpResponse.statusCode)")
6052
+ }
6053
+ }
6054
+ }
6055
+ task.resume()
6056
+ }
6057
+
6058
+ // Helper function to extract the amount from the URI
6059
+ func extractAmount(from uri: String) -> String? {
6060
+ let regex = try? NSRegularExpression(pattern: "amount=([\\d.]+)", options: [])
6061
+ let matches = regex?.matches(in: uri, options: [], range: NSRange(location: 0, length: uri.count))
6062
+
6063
+ if let match = matches?.first, let amountRange = Range(match.range(at: 1), in: uri) {
6064
+ return String(uri[amountRange])
6065
+ }
6066
+ return nil
6067
+ }
6068
+
6069
+ //MARK: - GET Crypto Payment Info API
6070
+ func getCryptoPaymentInfoApi(chargeId: String) {
6071
+
6072
+ let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.charges.path()+"/\(chargeId)"
6073
+
6074
+ guard let serviceURL = URL(string: fullURL) else {
6075
+ print("Invalid URL")
6076
+ hideLoadingIndicator()
6077
+ return
6078
+ }
6079
+
6080
+ var request = URLRequest(url: serviceURL)
6081
+ request.httpMethod = "GET"
6082
+ request.addValue("application/json", forHTTPHeaderField: "Content-Type")
6083
+
6084
+ let token = UserStoreSingleton.shared.clientToken
6085
+ print("Setting clientToken header: \(token ?? "None")")
6086
+ request.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
6087
+
6088
+ // Add API headers
6089
+ if let apiKey = EnvironmentConfig.apiKey,
6090
+ let apiSecret = EnvironmentConfig.apiSecret {
6091
+ request.addValue(apiKey, forHTTPHeaderField: "x-api-key")
6092
+ request.addValue(apiSecret, forHTTPHeaderField: "x-api-secret")
6093
+ }
6094
+
6095
+ let session = URLSession.shared
6096
+ let task = session.dataTask(with: request) { [weak self] (data, response, error) in
6097
+ guard let self = self else { return }
6098
+
6099
+ if let error = error {
6100
+ print("Error: \(error.localizedDescription)")
6101
+ return
6102
+ }
6103
+
6104
+ guard let data = data else { return }
6105
+
6106
+ do {
6107
+ if let jsonResponse = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
6108
+ let responseData = jsonResponse["data"] as? [String: Any] {
6109
+
6110
+ // Extract required data
6111
+ let amount = responseData["amount"] as? String ?? ""
6112
+ let dateCreated = responseData["date_created"] as? String ?? ""
6113
+ let status = responseData["status"] as? String ?? ""
6114
+ let transactionId = responseData["transaction_id"] as? String ?? ""
6115
+
6116
+ print("Amount: \(amount), Date Created: \(dateCreated), Status: \(status), Transaction ID: \(transactionId)")
6117
+
6118
+ // Check if the status is "completed"
6119
+ if status.lowercased() == "completed" {
6120
+ DispatchQueue.main.async {
6121
+ // Navigate to PaymentDoneVC and pass data
6122
+ self.navigateToPaymentDoneVC(amount: amount, dateCreated: dateCreated, transactionId: transactionId)
6123
+ }
6124
+ }
6125
+ }
6126
+ } catch {
6127
+ print("Failed to parse response: \(error.localizedDescription)")
6128
+ }
6129
+ }
6130
+ task.resume()
6131
+ }
6132
+
6133
+ // MARK: - 3DS Functionality
6134
+
6135
+ // MARK: - Credit Card Charge Api If Billing info is nil and Without Login.
6136
+ func threeDSecurePaymentApi() {
6137
+ showLoadingIndicator()
6138
+
6139
+ let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.threeDSecure.path()
6140
+
6141
+ guard let serviceURL = URL(string: fullURL) else {
6142
+ print("Invalid URL")
6143
+ hideLoadingIndicator()
6144
+ return
6145
+ }
6146
+
6147
+ var uRLRequest = URLRequest(url: serviceURL)
6148
+ uRLRequest.httpMethod = "POST"
6149
+ uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
6150
+
6151
+ let token = UserStoreSingleton.shared.clientToken
6152
+ print("Setting clientToken header: \(token ?? "None")")
6153
+ uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
5114
6154
 
5115
6155
  // Add API headers
5116
6156
  if let apiKey = EnvironmentConfig.apiKey,
5117
6157
  let apiSecret = EnvironmentConfig.apiSecret {
5118
- request.addValue(apiKey, forHTTPHeaderField: "x-api-key")
5119
- request.addValue(apiSecret, forHTTPHeaderField: "x-api-secret")
6158
+ uRLRequest.addValue(apiKey, forHTTPHeaderField: "x-api-key")
6159
+ uRLRequest.addValue(apiSecret, forHTTPHeaderField: "x-api-secret")
5120
6160
  }
5121
6161
 
5122
- let params: [String: Any] = [
5123
- "name": UserStoreSingleton.shared.merchantName ?? "",
6162
+ var params: [String: Any] = [
6163
+ "name": cardNameTextField.text,
5124
6164
  "email": UserStoreSingleton.shared.merchantEmail ?? "",
5125
- "description": "Payment checkout",
5126
- "amount": amount ?? "",
5127
- "currency": "satoshi",
5128
- "payment_account": UserStoreSingleton.shared.paymentAccount ?? "",
5129
- "success_url": "https://stage-api.stage-easymerchant.io/webhook/crypto",
5130
- "callback_url": "https://stage-api.stage-easymerchant.io/webhook/crypto"
6165
+ "card_number": cardNumberTextField.text.replacingOccurrences(of: " ", with: ""),
6166
+ "cardholder_name": cardNameTextField.text,
6167
+ "exp_month": cardExpiryTextField.text.components(separatedBy: "/").first ?? "",
6168
+ "exp_year": cardExpiryTextField.text.components(separatedBy: "/").last ?? "",
6169
+ "cvc": cardCvvTextField.text,
6170
+ "description": "payment checkout",
6171
+ "currency": "usd",
6172
+ "payment_method": "card",
6173
+ "tokenize": request.tokenOnly ?? false,
5131
6174
  ]
5132
6175
 
5133
- print(params)
6176
+ // Add these if recurring is enabled
6177
+ if let req = request, req.is_recurring == true {
6178
+ if let recurringType = req.recurringStartDateType, recurringType == .custom {
6179
+ // Only send start_date if type is .custom and field is not empty
6180
+ if let startDateText = txtFieldStartDateCard?.text, !startDateText.isEmpty {
6181
+ let inputFormatter = DateFormatter()
6182
+ inputFormatter.dateFormat = "dd/MM/yyyy"
6183
+
6184
+ let outputFormatter = DateFormatter()
6185
+ outputFormatter.dateFormat = "MM/dd/yyyy"
6186
+
6187
+ if let date = inputFormatter.date(from: startDateText) {
6188
+ let apiFormattedDate = outputFormatter.string(from: date)
6189
+ params["start_date"] = apiFormattedDate
6190
+ } else {
6191
+ print("Invalid date format in startDateText")
6192
+ }
6193
+ }
6194
+ }
6195
+
6196
+ params["interval"] = txtFieldChosePlanCard.text.lowercased()
6197
+ }
5134
6198
 
5135
6199
  do {
5136
6200
  let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
5137
- request.httpBody = jsonData
6201
+ uRLRequest.httpBody = jsonData
5138
6202
  if let jsonString = String(data: jsonData, encoding: .utf8) {
5139
6203
  print("JSON Payload: \(jsonString)")
5140
6204
  }
@@ -5145,7 +6209,7 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
5145
6209
  }
5146
6210
 
5147
6211
  let session = URLSession.shared
5148
- let task = session.dataTask(with: request) { (serviceData, serviceResponse, error) in
6212
+ let task = session.dataTask(with: uRLRequest) { (serviceData, serviceResponse, error) in
5149
6213
 
5150
6214
  DispatchQueue.main.async {
5151
6215
  self.hideLoadingIndicator() // Stop loader when response is received
@@ -5172,40 +6236,16 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
5172
6236
  let errorMessage = responseObject["message"] as? String ?? "Unknown error"
5173
6237
  self.presentPaymentErrorVC(errorMessage: errorMessage)
5174
6238
  } else {
5175
- // Extract charge_id
5176
- if let chargeId = responseObject["charge_id"] as? String {
5177
- self.chargeId = chargeId
5178
- print("Charge ID: \(chargeId)")
5179
- }
5180
-
5181
- // Extract chain_invoice address and lightning invoice fields
5182
- if let data = responseObject["data"] as? [String: Any] {
5183
- self.chainInvoiceAddress = (data["chain_invoice"] as? [String: Any])?["address"] as? String
5184
- self.lightningURI = (data["lightning_invoice"] as? [String: Any])?["payreq"] as? String
5185
-
5186
- // Default to OnChain QR code initially
5187
- DispatchQueue.main.async {
5188
- if let address = self.chainInvoiceAddress {
5189
- self.qrImageView.image = self.generateQRCode(from: address)
5190
- self.lblBTCAddress.text = "BTC Address: \(address)"
5191
-
5192
- // Extract the amount from the URI
5193
- if let uri = data["uri"] as? String {
5194
- if let amountString = self.extractAmount(from: uri) {
5195
- // Update the label with the extracted amount
5196
- DispatchQueue.main.async {
5197
- self.lblAmmoutCrypto.text = "\(amountString) BTC = $\(self.amount ?? 0) USD"
5198
- self.lblCryptoAmmountViewTryAgain.text = "\(amountString) BTC = $\(self.amount ?? 0) USD"
5199
- }
5200
- }
5201
- }
5202
- }
6239
+ DispatchQueue.main.async {
6240
+ if let paymentDoneVC = self.storyboard?.instantiateViewController(withIdentifier: "ThreeDSecurePaymentDoneVC") as? ThreeDSecurePaymentDoneVC {
5203
6241
 
5204
- self.viewCryptoQRCode.isHidden = false
5205
- self.viewCryptoTryAgain.isHidden = true
6242
+ let urlString = responseObject["redirect_url"] as? String ?? responseObject["location_url"] as? String ?? ""
6243
+ paymentDoneVC.redirectURL = urlString
6244
+ paymentDoneVC.chargeData = responseObject
6245
+ paymentDoneVC.easyPayDelegate = self.easyPayDelegate
6246
+
6247
+ self.navigationController?.pushViewController(paymentDoneVC, animated: true)
5206
6248
  }
5207
- } else {
5208
- self.presentPaymentErrorVC(errorMessage: "Invalid response format")
5209
6249
  }
5210
6250
  }
5211
6251
  } else {
@@ -5230,21 +6270,77 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
5230
6270
  task.resume()
5231
6271
  }
5232
6272
 
5233
- // Helper function to extract the amount from the URI
5234
- func extractAmount(from uri: String) -> String? {
5235
- let regex = try? NSRegularExpression(pattern: "amount=([\\d.]+)", options: [])
5236
- let matches = regex?.matches(in: uri, options: [], range: NSRange(location: 0, length: uri.count))
6273
+ // MARK: - Credit Card Charge Api If Billing info is nil from New Card.
6274
+ func threeDSecurePaymentNewCardApi(customerId: String) {
6275
+ // Get the text fields from the selected cell and trim whitespace
6276
+ let nameText = txtFieldNameOnCardNewCardView.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
6277
+ let cvvText = txtFieldCVVNewCardView.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
6278
+ let cardNumberText = (txtFieldCardNumberNewCardView.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "").replacingOccurrences(of: " ", with: "")
6279
+ let expiryText = txtFieldExpiryDateNewCardView.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
5237
6280
 
5238
- if let match = matches?.first, let amountRange = Range(match.range(at: 1), in: uri) {
5239
- return String(uri[amountRange])
6281
+ // Check if any field is empty
6282
+ if cardNumberText.isEmpty || expiryText.isEmpty || cvvText.isEmpty || nameText.isEmpty {
6283
+ let alert = UIAlertController(title: "Missing Information", message: "Please fill in all card details.", preferredStyle: .alert)
6284
+ alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
6285
+ self.present(alert, animated: true, completion: nil)
6286
+ return
5240
6287
  }
5241
- return nil
5242
- }
5243
-
5244
- //MARK: - GET Crypto Payment Info API
5245
- func getCryptoPaymentInfoApi(chargeId: String) {
5246
6288
 
5247
- let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.charges.path()+"/\(chargeId)"
6289
+ // Validate expiry date format
6290
+ let expiryDateFormat = "MM/yyyy"
6291
+ let dateFormatter = DateFormatter()
6292
+ dateFormatter.dateFormat = expiryDateFormat
6293
+ dateFormatter.locale = Locale(identifier: "en_US_POSIX")
6294
+
6295
+ // Split the expiry date and validate its format
6296
+ let exp = expiryText.split(separator: "/")
6297
+ if exp.count != 2 || exp[0].count != 2 || exp[1].count != 4 {
6298
+ let alert = UIAlertController(title: "Invalid Expiry Date", message: "Please enter the expiry date in the format MM/yyyy.", preferredStyle: .alert)
6299
+ alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
6300
+ self.present(alert, animated: true, completion: nil)
6301
+ return
6302
+ }
6303
+
6304
+ // Check if the expiry date is valid
6305
+ if let expiryDateObj = dateFormatter.date(from: expiryText) {
6306
+ // Check if the expiry date is in the past
6307
+ let currentDate = Date()
6308
+ let calendar = Calendar.current
6309
+ let currentMonthYear = calendar.date(from: calendar.dateComponents([.year, .month], from: currentDate))
6310
+ if expiryDateObj < currentMonthYear! {
6311
+ let alert = UIAlertController(title: "Expired Card", message: "The expiry date cannot be in the past. Please enter a valid expiry date.", preferredStyle: .alert)
6312
+ alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
6313
+ self.present(alert, animated: true, completion: nil)
6314
+ return
6315
+ }
6316
+ } else {
6317
+ let alert = UIAlertController(title: "Invalid Expiry Date", message: "Please enter the expiry date in the format MM/yyyy.", preferredStyle: .alert)
6318
+ alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
6319
+ self.present(alert, animated: true, completion: nil)
6320
+ return
6321
+ }
6322
+
6323
+ // Recurring Plan + Date Validation (only if is_recurring is true)
6324
+ if let req = self.request, req.is_recurring == true {
6325
+ let planText = txtFieldSelectPlanNewCardView.text.trimmingCharacters(in: .whitespacesAndNewlines)
6326
+ let dateText = txtFieldSelectDateNewCardView.text.trimmingCharacters(in: .whitespacesAndNewlines)
6327
+
6328
+ if planText.isEmpty {
6329
+ self.showAlert(title: "Missing Information", message: "Please choose your plan.")
6330
+ return
6331
+ }
6332
+
6333
+ if let recurringType = req.recurringStartDateType, recurringType == .custom {
6334
+ if dateText.isEmpty {
6335
+ self.showAlert(title: "Missing Information", message: "Please select a plan start date.")
6336
+ return
6337
+ }
6338
+ }
6339
+ }
6340
+
6341
+ showLoadingIndicator()
6342
+
6343
+ let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.threeDSecure.path()
5248
6344
 
5249
6345
  guard let serviceURL = URL(string: fullURL) else {
5250
6346
  print("Invalid URL")
@@ -5252,54 +6348,133 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
5252
6348
  return
5253
6349
  }
5254
6350
 
5255
- var request = URLRequest(url: serviceURL)
5256
- request.httpMethod = "GET"
5257
- request.addValue("application/json", forHTTPHeaderField: "Content-Type")
6351
+ var uRLRequest = URLRequest(url: serviceURL)
6352
+ uRLRequest.httpMethod = "POST"
6353
+ uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
5258
6354
 
5259
6355
  let token = UserStoreSingleton.shared.clientToken
5260
6356
  print("Setting clientToken header: \(token ?? "None")")
5261
- request.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
6357
+ uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
5262
6358
 
5263
6359
  // Add API headers
5264
6360
  if let apiKey = EnvironmentConfig.apiKey,
5265
6361
  let apiSecret = EnvironmentConfig.apiSecret {
5266
- request.addValue(apiKey, forHTTPHeaderField: "x-api-key")
5267
- request.addValue(apiSecret, forHTTPHeaderField: "x-api-secret")
6362
+ uRLRequest.addValue(apiKey, forHTTPHeaderField: "x-api-key")
6363
+ uRLRequest.addValue(apiSecret, forHTTPHeaderField: "x-api-secret")
6364
+ }
6365
+
6366
+ var params: [String: Any] = [
6367
+ "name": nameText,
6368
+ "email": txtFieldEmail.text ?? "",
6369
+ "card_number": cardNumberText,
6370
+ "cardholder_name": nameText,
6371
+ "exp_month": expiryText.components(separatedBy: "/").first ?? "",
6372
+ "exp_year": expiryText.components(separatedBy: "/").last ?? "",
6373
+ "cvc": cvvText,
6374
+ "description": "payment checkout",
6375
+ "currency": "usd",
6376
+ "tokenize": request.tokenOnly ?? false,
6377
+ "customer_id": customerId,
6378
+ "save_card": isSavedNewCardForFuture ? 1 : 0,
6379
+ ]
6380
+
6381
+ // Add is_default parameter if save_card is 1
6382
+ if isSavedNewCardForFuture {
6383
+ params["is_default"] = "1"
6384
+ }
6385
+
6386
+ // Add these if recurring is enabled
6387
+ if let req = request, req.is_recurring == true {
6388
+ if let recurringType = req.recurringStartDateType, recurringType == .custom {
6389
+ // Only send start_date if type is .custom and field is not empty
6390
+ if let startDateText = txtFieldSelectDateNewCardView?.text, !startDateText.isEmpty {
6391
+ let inputFormatter = DateFormatter()
6392
+ inputFormatter.dateFormat = "dd/MM/yyyy"
6393
+
6394
+ let outputFormatter = DateFormatter()
6395
+ outputFormatter.dateFormat = "MM/dd/yyyy"
6396
+
6397
+ if let date = inputFormatter.date(from: startDateText) {
6398
+ let apiFormattedDate = outputFormatter.string(from: date)
6399
+ params["start_date"] = apiFormattedDate
6400
+ } else {
6401
+ print("Invalid date format in startDateText")
6402
+ }
6403
+ }
6404
+ }
6405
+
6406
+ params["interval"] = txtFieldSelectPlanNewCardView.text.lowercased()
6407
+ }
6408
+
6409
+ do {
6410
+ let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
6411
+ uRLRequest.httpBody = jsonData
6412
+ if let jsonString = String(data: jsonData, encoding: .utf8) {
6413
+ print("JSON Payload: \(jsonString)")
6414
+ }
6415
+ } catch let error {
6416
+ print("Error creating JSON data: \(error)")
6417
+ hideLoadingIndicator()
6418
+ return
5268
6419
  }
5269
6420
 
5270
6421
  let session = URLSession.shared
5271
- let task = session.dataTask(with: request) { [weak self] (data, response, error) in
5272
- guard let self = self else { return }
6422
+ let task = session.dataTask(with: uRLRequest) { (serviceData, serviceResponse, error) in
6423
+
6424
+ DispatchQueue.main.async {
6425
+ self.hideLoadingIndicator() // Stop loader when response is received
6426
+ }
5273
6427
 
5274
6428
  if let error = error {
5275
6429
  print("Error: \(error.localizedDescription)")
5276
6430
  return
5277
6431
  }
5278
6432
 
5279
- guard let data = data else { return }
6433
+ guard let httpResponse = serviceResponse as? HTTPURLResponse else {
6434
+ print("Invalid response")
6435
+ return
6436
+ }
5280
6437
 
5281
- do {
5282
- if let jsonResponse = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
5283
- let responseData = jsonResponse["data"] as? [String: Any] {
5284
-
5285
- // Extract required data
5286
- let amount = responseData["amount"] as? String ?? ""
5287
- let dateCreated = responseData["date_created"] as? String ?? ""
5288
- let status = responseData["status"] as? String ?? ""
5289
- let transactionId = responseData["transaction_id"] as? String ?? ""
5290
-
5291
- print("Amount: \(amount), Date Created: \(dateCreated), Status: \(status), Transaction ID: \(transactionId)")
5292
-
5293
- // Check if the status is "completed"
5294
- if status.lowercased() == "completed" {
5295
- DispatchQueue.main.async {
5296
- // Navigate to PaymentDoneVC and pass data
5297
- self.navigateToPaymentDoneVC(amount: amount, dateCreated: dateCreated, transactionId: transactionId)
6438
+ if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
6439
+ if let data = serviceData {
6440
+ do {
6441
+ if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
6442
+ print("Response Data: \(responseObject)")
6443
+
6444
+ // Check if status is 0 and handle the error
6445
+ if let status = responseObject["status"] as? Int, status == 0 {
6446
+ let errorMessage = responseObject["message"] as? String ?? "Unknown error"
6447
+ self.presentPaymentErrorVC(errorMessage: errorMessage)
6448
+ } else {
6449
+ DispatchQueue.main.async {
6450
+ if let paymentDoneVC = self.storyboard?.instantiateViewController(withIdentifier: "ThreeDSecurePaymentDoneVC") as? ThreeDSecurePaymentDoneVC {
6451
+
6452
+ let urlString = responseObject["redirect_url"] as? String ?? responseObject["location_url"] as? String ?? ""
6453
+ paymentDoneVC.redirectURL = urlString
6454
+ paymentDoneVC.chargeData = responseObject
6455
+ paymentDoneVC.easyPayDelegate = self.easyPayDelegate
6456
+
6457
+ self.navigationController?.pushViewController(paymentDoneVC, animated: true)
6458
+ }
6459
+ }
6460
+ }
6461
+ } else {
6462
+ self.presentPaymentErrorVC(errorMessage: "Invalid JSON format")
5298
6463
  }
6464
+ } catch let jsonError {
6465
+ self.presentPaymentErrorVC(errorMessage: "Error parsing JSON: \(jsonError)")
5299
6466
  }
6467
+ } else {
6468
+ self.presentPaymentErrorVC(errorMessage: "No data received")
6469
+ }
6470
+ } else {
6471
+ if let data = serviceData,
6472
+ let responseObj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
6473
+ let message = responseObj["message"] as? String {
6474
+ self.presentPaymentErrorVC(errorMessage: message)
6475
+ } else {
6476
+ self.presentPaymentErrorVC(errorMessage: "HTTP Status Code: \(httpResponse.statusCode)")
5300
6477
  }
5301
- } catch {
5302
- print("Failed to parse response: \(error.localizedDescription)")
5303
6478
  }
5304
6479
  }
5305
6480
  task.resume()
@@ -5341,9 +6516,17 @@ extension PaymentInfoVC: UICollectionViewDelegate, UICollectionViewDataSource, U
5341
6516
  cell.lblPayment.textColor = uiColor
5342
6517
  }
5343
6518
  } else {
5344
- cell.vwMain.layer.borderColor = UIColor.gray.cgColor
5345
- cell.imgVwPayments.tintColor = .systemGray
5346
- cell.lblPayment.textColor = .systemGray
6519
+ if let primaryFontColor = UserStoreSingleton.shared.primary_font_col,
6520
+ let uiColor = UIColor(hex: primaryFontColor) {
6521
+ cell.vwMain.layer.borderColor = uiColor.cgColor
6522
+ cell.imgVwPayments.tintColor = uiColor
6523
+ cell.lblPayment.textColor = uiColor
6524
+ }
6525
+ else {
6526
+ cell.vwMain.layer.borderColor = UIColor.gray.cgColor
6527
+ cell.imgVwPayments.tintColor = .systemGray
6528
+ cell.lblPayment.textColor = .systemGray
6529
+ }
5347
6530
  }
5348
6531
 
5349
6532
  return cell
@@ -5446,9 +6629,11 @@ extension PaymentInfoVC: UICollectionViewDelegate, UICollectionViewDataSource, U
5446
6629
  self.btnNext.isHidden = true
5447
6630
  self.btnNextHeight.constant = 0
5448
6631
  self.btnNextTopCon.constant = 0
5449
- if self.request.enableRecurring == true {
6632
+ if self.request.is_recurring == true {
5450
6633
  self.txtFieldSelectPlanSingleSavedCard.isHidden = false
5451
- self.txtFieldSelectDateSingleSavedCard.isHidden = false
6634
+ if request.recurringStartDateType == .custom {
6635
+ self.txtFieldSelectDateSingleSavedCard.isHidden = false
6636
+ }
5452
6637
  }
5453
6638
  else {
5454
6639
  self.txtFieldSelectPlanSingleSavedCard.isHidden = true
@@ -5620,10 +6805,10 @@ extension PaymentInfoVC: UITableViewDelegate, UITableViewDataSource {
5620
6805
  cell.btnRemoveBankAccount.removeTarget(nil, action: nil, for: .allEvents)
5621
6806
 
5622
6807
  if indexPath.row == bankAccounts.count {
5623
- print("Showing 'Add New Account' cell")
6808
+ // print("Showing 'Add New Account' cell")
5624
6809
  configureAddNewAccountCell(cell)
5625
6810
  } else {
5626
- print("Configuring saved account cell at row: \(indexPath.row)")
6811
+ // print("Configuring saved account cell at row: \(indexPath.row)")
5627
6812
  configureSavedAccountCell(cell, at: indexPath)
5628
6813
  }
5629
6814
 
@@ -5962,11 +7147,9 @@ extension PaymentInfoVC: UITableViewDelegate, UITableViewDataSource {
5962
7147
  if isSelectForPayBank {
5963
7148
  viewTermAndConditionsSingleAccountView.isHidden = false
5964
7149
  btnPayNowSingleAccountView.isHidden = false
5965
- // viewSingleAccountViewHeight.constant = 170
5966
7150
  } else {
5967
7151
  viewTermAndConditionsSingleAccountView.isHidden = true
5968
7152
  btnPayNowSingleAccountView.isHidden = true
5969
- // viewSingleAccountViewHeight.constant = 68
5970
7153
  btnNext.isHidden = true
5971
7154
  btnNextHeight.constant = 0
5972
7155
  btnNextTopCon.constant = 0
@@ -6069,7 +7252,7 @@ extension PaymentInfoVC: UITextFieldDelegate {
6069
7252
  }
6070
7253
 
6071
7254
  // Handle card CVV input for both old and new card fields
6072
- else if textField == cardCvvTextField.textField || textField == txtFieldCVVNewCardView || textField == txtFieldCVVUpdateCardView {
7255
+ else if textField == cardCvvTextField.textField || textField == txtFieldCVVNewCardView || textField == txtFieldCVVUpdateCardView || textField == txtFieldCVVSingleSavedCard {
6073
7256
  let maxLength = 3
6074
7257
  let currentString = (textField.text ?? "") as NSString
6075
7258
  let newString = currentString.replacingCharacters(in: range, with: string)
@@ -6675,11 +7858,9 @@ extension PaymentInfoVC: UITextFieldDelegate {
6675
7858
  //
6676
7859
  // func blinkCardOverlayViewControllerDidFinishEditing(_ blinkCardOverlayViewController: MBCBlinkCardOverlayViewController, editResult: MBCBlinkCardEditResult) {
6677
7860
  // blinkCardOverlayViewController.recognizerRunnerViewController?.pauseScanning()
6678
- //
6679
7861
  // /** Needs to be called on main thread beacuse everything prior is on background thread */
6680
7862
  // DispatchQueue.main.async {
6681
7863
  // // present the alert view with scanned results
6682
- //
6683
7864
  // let alertController: UIAlertController = UIAlertController.init(title: "BlinkCard Edit Results", message: editResult.description, preferredStyle: .alert)
6684
7865
  //
6685
7866
  // let okAction: UIAlertAction = UIAlertAction.init(title: "OK", style: .default,
@@ -6692,7 +7873,7 @@ extension PaymentInfoVC: UITextFieldDelegate {
6692
7873
  // }
6693
7874
  //
6694
7875
  //}
6695
- //
7876
+
6696
7877
  //extension PaymentInfoVC: CustomOverlayDelegate {
6697
7878
  // func didFinishScanningCard(cardNumber: String, expiryDate: String, cvv: String, nameOnCard: String) {
6698
7879
  // // Update the text fields with scanned details