@jimrising/easymerchantsdk-react-native 1.3.0 → 1.3.2

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.
@@ -6,6 +6,7 @@
6
6
  //
7
7
 
8
8
  import UIKit
9
+ //import BlinkCard
9
10
 
10
11
  struct PaymentsData {
11
12
  var name = String()
@@ -15,7 +16,7 @@ struct PaymentsData {
15
16
  let easymerchantsdk = UIStoryboard(name: "easymerchantsdk", bundle: Bundle.easyPayBundle)
16
17
 
17
18
  class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
18
-
19
+
19
20
  @IBOutlet weak var btnShowSavedCard: UIButton!
20
21
  @IBOutlet weak var viewContainerBackground: UIView!
21
22
  @IBOutlet weak var lblChosePaymentMethod: UILabel!
@@ -36,25 +37,25 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
36
37
  @IBOutlet weak var lblNewAccount: UILabel!
37
38
  @IBOutlet weak var lblCreateNewAccount: UILabel!
38
39
  @IBOutlet weak var lblSetting: UILabel!
39
-
40
+
40
41
  @IBOutlet weak var viewBtnShowSavedCards: UIView!
41
42
  @IBOutlet weak var viewBtnShowSavedCardHeight: NSLayoutConstraint!
42
43
  @IBOutlet weak var viewBtnShowSavedCardTopCon: NSLayoutConstraint!
43
44
  @IBOutlet weak var lblBtnShowSaveCard: UILabel!
44
45
  @IBOutlet weak var iconShowSavedCard: UIImageView!
45
-
46
+
46
47
  @IBOutlet weak var collVwPayment: UICollectionView!
47
-
48
+
48
49
  @IBOutlet weak var viewCardFields: UIView!
49
-
50
+
50
51
  @IBOutlet weak var btnNext: FilledButton!
51
52
  @IBOutlet weak var btnNextHeight: NSLayoutConstraint!
52
53
  @IBOutlet weak var btnNextTopCon: NSLayoutConstraint!
53
-
54
+
54
55
  @IBOutlet weak var btnCheckBoxSavedCard: UIButton!
55
56
  @IBOutlet weak var lblEasyMerchantOne: UILabel!
56
57
  @IBOutlet weak var btnSettings: UIButton!
57
-
58
+
58
59
  // Card
59
60
  @IBOutlet private var cardNumberTextField: TextFieldStackView!
60
61
  @IBOutlet private var cardExpiryTextField: TextFieldStackView!
@@ -64,14 +65,14 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
64
65
  @IBOutlet weak var viewTxtFieldExpiryDate: UIView!
65
66
  @IBOutlet weak var viewTxtFieldCVV: UIView!
66
67
  @IBOutlet weak var viewTxtFieldNameOnCard: UIView!
67
-
68
+
68
69
  @IBOutlet weak var btnScanCard: UIButton!
69
-
70
+
70
71
  @IBOutlet weak var emailView: UIView!
71
72
  @IBOutlet weak var txtFieldEmail: UITextField!
72
73
  @IBOutlet weak var viewTxtFieldEmail: UIView!
73
74
  @IBOutlet weak var OTPView: UIView!
74
-
75
+
75
76
  //OTP View
76
77
  @IBOutlet weak var viewTextOTP1: UIView!
77
78
  @IBOutlet weak var txtFieldOTPText1: UITextField!
@@ -89,9 +90,9 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
89
90
  @IBOutlet weak var lblOtpTimer: UILabel!
90
91
  @IBOutlet weak var lblUntillResendOtp: UILabel!
91
92
  @IBOutlet weak var btnResendOTP: UIButton!
92
-
93
+
93
94
  @IBOutlet private var buttonBottomConstraint: NSLayoutConstraint!
94
-
95
+
95
96
  //Single Saved Card
96
97
  @IBOutlet weak var viewSingleSavedCard: UIView!
97
98
  @IBOutlet weak var viewSingleSavedCardHeight: NSLayoutConstraint!
@@ -103,16 +104,16 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
103
104
  @IBOutlet weak var lblCardNumberSigleSavedCard: UILabel!
104
105
  @IBOutlet weak var lblExpireDateSingelSavedCard: UILabel!
105
106
  @IBOutlet weak var bgViewSingleSavedCard: UIView!
106
-
107
+
107
108
  @IBOutlet weak var viewChangeCard: UIView!
108
109
  @IBOutlet weak var viewUpdateCard: UIView!
109
110
  @IBOutlet weak var viewAddNewCard: UIView!
110
111
  @IBOutlet weak var btnChangeSavedCard: UIButton!
111
112
  @IBOutlet weak var btnSelectSingleSavedCard: UIButton!
112
-
113
+
113
114
  @IBOutlet weak var tblViewSavedCardsList: UITableView!
114
115
  @IBOutlet weak var tblViewSavedCardListHeight: NSLayoutConstraint!
115
-
116
+
116
117
  //Update Card
117
118
  @IBOutlet weak var btnSelectUpdateCardView: UIButton!
118
119
  @IBOutlet weak var imgViewCardUpdateCardView: UIImageView!
@@ -126,7 +127,7 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
126
127
  @IBOutlet weak var viewTxtFieldNameOnCardUpdateCardView: UIView!
127
128
  @IBOutlet weak var btnUpdateCard: FilledButton!
128
129
  @IBOutlet weak var bgViewUpdateCard: UIView!
129
-
130
+
130
131
  //New Card
131
132
  @IBOutlet weak var txtFieldCardNumberNewCardView: UITextField!
132
133
  @IBOutlet weak var viewtxtFieldCardNumberNewCardView: UIView!
@@ -138,7 +139,7 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
138
139
  @IBOutlet weak var viewtxtFieldNameOnCardNewCardView: UIView!
139
140
  @IBOutlet weak var btnPayNowNewCardView: FilledButton!
140
141
  @IBOutlet weak var btnSavedCardForFutureNewCardView: CheckboxButton!
141
-
142
+
142
143
  //Bank
143
144
  @IBOutlet weak var viewBankFields: UIView!
144
145
  @IBOutlet weak var txtFieldAccountName: UITextField!
@@ -155,7 +156,9 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
155
156
  @IBOutlet weak var viewAccountType: UIView!
156
157
  @IBOutlet weak var tblViewAccountTypes: UITableView!
157
158
  @IBOutlet weak var btnCheckSavedAccountForFuture: UIButton!
158
-
159
+ @IBOutlet weak var txtFieldConfirmBankAccount: UITextField!
160
+ @IBOutlet weak var viewTextFieldConfirmAccount: UIView!
161
+
159
162
  //GrailPay
160
163
  @IBOutlet weak var grailPayBankLinkView: UIView!
161
164
  @IBOutlet weak var viewGrailPaySavedBank: UIStackView!
@@ -172,7 +175,27 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
172
175
  @IBOutlet weak var viewGrailPayAbandon: UIView!
173
176
  @IBOutlet weak var imgGrailPayAbandonError: UIImageView!
174
177
  @IBOutlet weak var lblGrailPayAabandonError: UILabel!
175
-
178
+
179
+ //New GrailPay Account
180
+ @IBOutlet weak var viewAddNewGrailPayAccount: UIView!
181
+ @IBOutlet weak var lblGrailPayNewAccount: UILabel!
182
+ @IBOutlet weak var subLblGrailPayNewAccount: UILabel!
183
+ @IBOutlet weak var btnCloseGrailPayNewAccountView: UIButton!
184
+ @IBOutlet weak var btnLinkGrailPayAddNewAccount: FilledButton!
185
+ @IBOutlet weak var viewAbandonGrailPayNewAccountView: UIView!
186
+ @IBOutlet weak var lblAbandonGrailPayNewAccountView: UILabel!
187
+ @IBOutlet weak var grailPayNewSavedAccountView: UIStackView!
188
+ @IBOutlet weak var viewGrailPayNewAccountDetail: UIView!
189
+ @IBOutlet weak var btnSelectGrailPayNewAccount: UIButton!
190
+ @IBOutlet weak var lblGrailPayNewAccountNumber: UILabel!
191
+ @IBOutlet weak var lblGrailPayNewAccountType: UILabel!
192
+ @IBOutlet weak var btnCheckBoxGrailPayNewAccountSaveAccount: UIButton!
193
+ @IBOutlet weak var lblGrailPayNewAccountSaveForFuture: UILabel!
194
+ @IBOutlet weak var btnCheckBoxGrailPayNewAccountTerms: UIButton!
195
+ @IBOutlet weak var lblIagreeGrailPayNewAccountView: UILabel!
196
+ @IBOutlet weak var lblTermsAndConditionNewGrailPayAccountView: UILabel!
197
+ @IBOutlet weak var btnChangeNewGrailPayAccountView: UIButton!
198
+
176
199
  //SavedBank
177
200
  @IBOutlet weak var viewSingleSavedAccount: UIView!
178
201
  @IBOutlet weak var viewTermAndConditionsSingleAccountView: UIStackView!
@@ -183,14 +206,14 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
183
206
  @IBOutlet weak var lblAccountNumberSingleAccountView: UILabel!
184
207
  @IBOutlet weak var lblAccountTypeSingleAccountView: UILabel!
185
208
  @IBOutlet weak var btnPayNowSingleAccountView: FilledButton!
186
- @IBOutlet weak var viewSingleAccountViewHeight: NSLayoutConstraint!
209
+ // @IBOutlet weak var viewSingleAccountViewHeight: NSLayoutConstraint!
187
210
  @IBOutlet weak var viewChangedAccount: UIView!
188
211
  @IBOutlet weak var tblViewSavedBankAccounts: UITableView!
189
212
  @IBOutlet weak var tblViewSavedBankAccountsHeight: NSLayoutConstraint!
190
213
  @IBOutlet weak var bankIconImgView: UIImageView!
191
214
  @IBOutlet weak var btnChangeSingleAccountView: UIButton!
192
215
  @IBOutlet weak var viewSingelBankAccount: UIView!
193
-
216
+
194
217
  //New Bank
195
218
  @IBOutlet weak var viewNewBankAccount: UIView!
196
219
  @IBOutlet weak var txtFieldAccountNameNewAccountView: UITextField!
@@ -206,7 +229,9 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
206
229
  @IBOutlet weak var tblViewAccountTypeNewAccountView: UITableView!
207
230
  @IBOutlet weak var btnPayNowNewAccountView: FilledButton!
208
231
  @IBOutlet weak var lblSavedNewAccountForFuture: UILabel!
209
-
232
+ @IBOutlet weak var txtFieldConfirmAccountNewAccountView: UITextField!
233
+ @IBOutlet weak var viewTxtFieldConfirmNewAccountView: UIView!
234
+
210
235
  //Crypto
211
236
  @IBOutlet weak var viewCrypto: UIView!
212
237
  @IBOutlet weak var viewCryptoTryAgain: UIView!
@@ -228,113 +253,118 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
228
253
  @IBOutlet weak var subLblPriceChanged: UILabel!
229
254
  @IBOutlet weak var btnTryAgain: UIButton!
230
255
  @IBOutlet weak var lblTimerTryAgainView: UILabel!
231
-
256
+
232
257
  //Setting View
233
258
  @IBOutlet weak var btnChangeLanguage: UIButton!
234
259
  @IBOutlet weak var btnLogout: UIButton!
235
260
  @IBOutlet weak var settingsView: UIView!
236
-
261
+
237
262
  var chainInvoiceAddress: String?
238
263
  var lightningURI: String?
239
-
264
+
240
265
  var cryptoTimer: DispatchSourceTimer?
241
266
  var totalTimeInSeconds = 300 // 5 minutes (300 seconds)
242
267
  var isTimerRunning = false
243
-
268
+
244
269
  var chargeId: String?
245
270
  var cryptoInfoTimer: Timer?
246
-
271
+
247
272
  var savedCards: [CardModel] = []
248
273
  var isSelectForPay: Bool = false
249
-
274
+
250
275
  var selectedCardIndex: Int? = nil
251
-
276
+
252
277
  private var request: Request!
253
278
  private weak var delegate: EasyPayViewControllerDelegate?
254
-
279
+
255
280
  public var amount: Int?
256
-
281
+
257
282
  var arrPaymentMethods: [PaymentsData] = []
258
-
283
+
259
284
  private var selectedIndex: IndexPath?
260
285
  var selectedPaymentMethod: String?
261
-
286
+
262
287
  private var timer: Timer?
263
288
  private var timeRemaining = 180 // 3 minutes
264
-
289
+
265
290
  var isSavedForFuture: Bool = false
266
-
291
+
267
292
  var selectedCard: CardModel?
268
-
293
+
269
294
  var isSavedNewCardForFuture: Bool = false
270
-
295
+
271
296
  var selectedCardID: String?
272
-
297
+
273
298
  //Bank
274
299
  var bankAccounts: [Accounts] = []
275
300
  var selectedbankAccounts: Accounts?
276
301
  var selectedBank: BankAccountModel?
277
302
  var isSelectForPayBank: Bool = false
278
303
  var selectedBankIndex: Int? = nil
279
- var arrAccountType = ["Saving","Current"]
280
-
304
+ var arrAccountType = ["Saving", "Checking"]
305
+
281
306
  var agreeTermsAndCondtition: Bool = false
282
-
307
+
283
308
  var isSavedNewAccount: Bool = false
284
309
  var selectedBankID: String?
285
-
310
+
286
311
  public weak var easyPayDelegate: EasyPayViewControllerDelegate?
287
-
312
+
288
313
  private let keyboardObserver = KeyboardObserver()
289
-
314
+
290
315
  var isFrom = String()
291
-
316
+
292
317
  //Blink Card
293
- // var blinkCardRecognizer: MBCBlinkCardRecognizer!
294
-
318
+ // var blinkCardRecognizer: MBCBlinkCardRecognizer!
319
+
295
320
  // Variables for Card Scanning Data Back
296
321
  var cardNumber: String?
297
322
  var expiryDate: String?
298
323
  var cvv: String?
299
324
  var nameOnCard: String?
300
-
325
+
301
326
  private var grailPayChargeData: [String: Any]?
302
-
327
+
303
328
  var grailPayAccount: [String: Any]?
304
-
329
+
305
330
  var isBankAccountConnected = false
306
331
  var defaultBankAccountID: String?
307
-
332
+
333
+ // var isNewBankAccountConnected = false
334
+ var defaultNewBankAccountID: String?
335
+
336
+ var isSavedNewGrailPayAccount: Bool = false
337
+
308
338
  //MARK: - View Did Load
309
339
  override func viewDidLoad() {
310
340
  super.viewDidLoad()
311
-
341
+
312
342
  viewAppearanceOn()
313
-
343
+
314
344
  uiFinishingTouchElements()
315
-
345
+
316
346
  setUpTextFieldsDelegates()
317
-
347
+
318
348
  collVwPayment.delegate = self
319
349
  collVwPayment.dataSource = self
320
-
350
+
321
351
  loadPaymentMethods()
322
-
352
+
323
353
  updateSaveButtons()
324
-
354
+
325
355
  // Add tap gesture to hide the keyboard when tapping outside of text fields
326
356
  let tapGesture = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
327
357
  tapGesture.cancelsTouchesInView = false
328
358
  self.view.addGestureRecognizer(tapGesture)
329
-
359
+
330
360
  //// Check if billingInfoData is available
331
361
  if let billingInfoData = request.billingInfoData,
332
362
  let json = try? JSONSerialization.jsonObject(with: billingInfoData, options: []),
333
363
  let jsonDict = json as? [String: Any], !jsonDict.isEmpty {
334
-
364
+
335
365
  // If `submitButtonText` is not empty, use it; otherwise, default to "Next (Billing Info)"
336
366
  let buttonText = (request?.submitButtonText?.isEmpty == false ? request!.submitButtonText! + " (Billing Info)" : "Next (Billing Info)")
337
-
367
+
338
368
  btnNext.setTitle(buttonText, for: .normal)
339
369
  btnPayNowNewCardView.setTitle(buttonText, for: .normal)
340
370
  btnPayNowNewAccountView.setTitle(buttonText, for: .normal)
@@ -343,29 +373,29 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
343
373
  } else {
344
374
  let amountText = String(format: "$%.2f", amount ?? 0)
345
375
  let submitText = request?.submitButtonText
346
-
376
+
347
377
  let defaultTitle = (submitText?.isEmpty == false)
348
378
  ? "\(submitText!) (\(amountText))"
349
379
  : "Pay Now (\(amountText))"
350
-
380
+
351
381
  btnNext.setTitle(defaultTitle, for: .normal)
352
382
  btnPayNowNewCardView.setTitle(defaultTitle, for: .normal)
353
383
  btnPayNowNewAccountView.setTitle(defaultTitle, for: .normal)
354
384
  btnPayNowSingleCard.setTitle(defaultTitle, for: .normal)
355
385
  btnPayNowSingleAccountView.setTitle(defaultTitle, for: .normal)
356
386
  }
357
-
387
+
358
388
  emailView.isHidden = true
359
389
  OTPView.isHidden = true
360
-
390
+
361
391
  //OTP View
362
392
  btnResendOTP.isHidden = true
363
-
393
+
364
394
  // Set delegate for txtFieldEmail to handle return key press
365
395
  txtFieldEmail.delegate = self
366
-
396
+
367
397
  setupTextFields()
368
-
398
+
369
399
  tblViewSavedCardsList.delegate = self
370
400
  tblViewSavedCardsList.dataSource = self
371
401
  let nib = UINib(nibName: "SavedCardsTVC", bundle: .easyPayBundle)
@@ -373,9 +403,9 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
373
403
  tblViewSavedCardsList.showsVerticalScrollIndicator = false
374
404
  tblViewSavedCardsList.showsHorizontalScrollIndicator = false
375
405
  tblViewSavedCardsList.addObserver(self, forKeyPath: "contentSize", options: .new, context: nil)
376
-
406
+
377
407
  viewChangeCard.isHidden = true
378
-
408
+
379
409
  tblViewSavedBankAccounts.delegate = self
380
410
  tblViewSavedBankAccounts.dataSource = self
381
411
  let nib2 = UINib(nibName: "SavedAccountTVC", bundle: .easyPayBundle)
@@ -383,14 +413,14 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
383
413
  tblViewSavedBankAccounts.showsVerticalScrollIndicator = false
384
414
  tblViewSavedBankAccounts.showsHorizontalScrollIndicator = false
385
415
  tblViewSavedBankAccounts.addObserver(self, forKeyPath: "contentSize", options: .new, context: nil)
386
-
416
+
387
417
  viewChangedAccount.isHidden = true
388
-
418
+
389
419
  tblViewAccountTypes.delegate = self
390
420
  tblViewAccountTypes.dataSource = self
391
421
  tblViewAccountTypes.showsVerticalScrollIndicator = false
392
422
  tblViewAccountTypes.showsHorizontalScrollIndicator = false
393
-
423
+
394
424
  viewAccountType.clipsToBounds = false
395
425
  viewAccountType.layer.shadowColor = UIColor.black.cgColor
396
426
  viewAccountType.layer.shadowOpacity = 0.3
@@ -398,14 +428,14 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
398
428
  viewAccountType.layer.shadowRadius = 4
399
429
  viewAccountType.layer.cornerRadius = 8
400
430
  viewAccountType.layer.cornerRadius = 8
401
-
431
+
402
432
  viewAccountType.isHidden = true
403
-
433
+
404
434
  tblViewAccountTypeNewAccountView.delegate = self
405
435
  tblViewAccountTypeNewAccountView.dataSource = self
406
436
  tblViewAccountTypeNewAccountView.showsVerticalScrollIndicator = false
407
437
  tblViewAccountTypeNewAccountView.showsHorizontalScrollIndicator = false
408
-
438
+
409
439
  viewAccountTypeNewAccountView.clipsToBounds = false
410
440
  viewAccountTypeNewAccountView.layer.shadowColor = UIColor.black.cgColor
411
441
  viewAccountTypeNewAccountView.layer.shadowOpacity = 0.3
@@ -413,16 +443,16 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
413
443
  viewAccountTypeNewAccountView.layer.shadowRadius = 4
414
444
  viewAccountTypeNewAccountView.layer.cornerRadius = 8
415
445
  viewAccountTypeNewAccountView.layer.cornerRadius = 8
416
-
446
+
417
447
  viewAccountTypeNewAccountView.isHidden = true
418
-
448
+
419
449
  // Generate and set the QR code image
420
450
  qrImageView.image = generateQRCode(from: "CryptoQR")
421
-
451
+
422
452
  setUpTermAndConditionsButton()
423
453
  setUpTermAndConditionsForSingleSavedAccountButton()
424
454
  setUpTermAndConditionsForGrailPaySavedAccountButton()
425
-
455
+
426
456
  settingsView.clipsToBounds = false
427
457
  settingsView.layer.shadowColor = UIColor.black.cgColor
428
458
  settingsView.layer.shadowOpacity = 0.3
@@ -430,57 +460,64 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
430
460
  settingsView.layer.shadowRadius = 4
431
461
  settingsView.layer.cornerRadius = 8
432
462
  settingsView.layer.cornerRadius = 8
433
-
463
+
434
464
  settingsView.isHidden = true
435
-
465
+
436
466
  if UserStoreSingleton.shared.isLoggedIn == true {
437
467
  btnSettings.isHidden = false
438
468
  }
439
469
  else {
440
470
  btnSettings.isHidden = true
441
471
  }
442
-
472
+
443
473
  }
444
-
474
+
445
475
  //MARK: - View Wiil Appear
446
476
  override func viewWillAppear(_ animated: Bool) {
447
477
  uiFinishingTouchElements()
448
-
478
+
449
479
  viewAppearanceOn()
450
-
480
+
451
481
  keyboardObserver.animateChanges({ [self] height in
452
482
  let newConstant = CGFloat.maximum(height - self.view.safeAreaInsets.bottom + 8, 8)
453
483
  buttonBottomConstraint.constant = newConstant
454
484
  self.view.setNeedsLayout()
455
485
  self.view.layoutIfNeeded()
456
486
  })
457
-
487
+
458
488
  //CRYPTO
459
489
  setOnChainSelected()
460
490
  }
461
-
491
+
462
492
  override func viewWillDisappear(_ animated: Bool) {
463
493
  super.viewWillDisappear(animated)
464
494
  keyboardObserver.invalidate()
465
495
  resetTimer()
466
496
  }
467
-
497
+
468
498
  //MARK: - View Appearances on clicks
469
499
  func viewAppearanceOn() {
470
500
  viewTermAndConditionsSingleAccountView.isHidden = true
471
501
  btnPayNowSingleAccountView.isHidden = true
472
- viewSingleAccountViewHeight.constant = 68
473
-
502
+ // viewSingleAccountViewHeight.constant = 68
503
+
474
504
  viewNewBankAccount.isHidden = true
475
-
505
+
476
506
  viewTxtFieldCVVSingleCard.isHidden = true
477
507
  btnPayNowSingleCard.isHidden = true
478
508
  viewSingleSavedCardHeight.constant = 60
479
-
509
+
480
510
  self.viewCrypto.isHidden = true
481
-
511
+
512
+ // self.viewAddNewGrailPayAccount.isHidden = true
513
+ // self.viewAbandonGrailPayNewAccountView.isHidden = true
514
+
482
515
  if selectedPaymentMethod == "Card" {
483
516
  self.grailPayBankLinkView.isHidden = true
517
+
518
+ self.viewAddNewGrailPayAccount.isHidden = true
519
+ self.viewAbandonGrailPayNewAccountView.isHidden = true
520
+
484
521
  if UserStoreSingleton.shared.isLoggedIn == true {
485
522
  if UserStoreSingleton.shared.customerId == nil {
486
523
  self.OTPView.isHidden = true
@@ -503,19 +540,19 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
503
540
  self.viewChangeCard.isHidden = true
504
541
  self.viewUpdateCard.isHidden = true
505
542
  self.viewAddNewCard.isHidden = true
506
-
543
+
507
544
  self.viewTxtFieldCVVSingleCard.isHidden = true
508
545
  self.btnPayNowSingleCard.isHidden = true
509
546
  self.viewSingleSavedCardHeight.constant = 60
510
547
  self.btnNext.isHidden = true
511
548
  self.btnNextHeight.constant = 0
512
549
  self.btnNextTopCon.constant = 8
513
-
550
+
514
551
  self.viewSingleSavedAccount.isHidden = true
515
-
552
+
516
553
  self.viewBankFields.isHidden = true
517
554
  self.viewTermsAndConditions.isHidden = true
518
-
555
+
519
556
  if isSelectForPay {
520
557
  viewTxtFieldCVVSingleCard.isHidden = false
521
558
  btnPayNowSingleCard.isHidden = false
@@ -556,13 +593,13 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
556
593
  }
557
594
  else if selectedPaymentMethod == "Bank" {
558
595
  if UserStoreSingleton.shared.isLoggedIn == true {
559
-
596
+
560
597
  if isFrom == "NewAccount" {
561
598
  viewNewBankAccount.isHidden = false
562
599
  } else {
563
600
  viewSingleSavedAccount.isHidden = false
564
601
  }
565
-
602
+
566
603
  self.viewSingleSavedAccount.isHidden = false
567
604
  self.viewBankFields.isHidden = true
568
605
  self.viewBtnShowSavedCards.isHidden = true
@@ -578,41 +615,45 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
578
615
  self.btnNext.isHidden = true
579
616
  self.btnNextHeight.constant = 0
580
617
  self.btnNextTopCon.constant = 8
581
-
618
+
582
619
  self.viewNewBankAccount.isHidden = true
583
620
  self.viewCrypto.isHidden = true
584
-
621
+
585
622
  self.viewTermsAndConditions.isHidden = true
586
-
623
+
587
624
  self.viewChangedAccount.isHidden = true
588
-
625
+
589
626
  if self.isSelectForPayBank {
590
627
  self.viewTermAndConditionsSingleAccountView.isHidden = false
591
628
  self.btnPayNowSingleAccountView.isHidden = false
592
- self.viewSingleAccountViewHeight.constant = 170
629
+ // self.viewSingleAccountViewHeight.constant = 170
593
630
  } else {
594
631
  self.viewTermAndConditionsSingleAccountView.isHidden = true
595
632
  self.btnPayNowSingleAccountView.isHidden = true
596
- self.viewSingleAccountViewHeight.constant = 68
633
+ // self.viewSingleAccountViewHeight.constant = 68
597
634
  self.btnNext.isHidden = true
598
635
  self.btnNextHeight.constant = 0
599
636
  self.btnNextTopCon.constant = 0
600
637
  }
601
-
638
+
602
639
  if request?.authenticatedACH == true {
603
- self.viewBankFields.isHidden = true
604
- self.viewTermsAndConditions.isHidden = true
605
- self.grailPayBankLinkView.isHidden = false
606
- self.btnNext.isHidden = true
607
- self.btnNextHeight.constant = 0
608
- self.btnNextTopCon.constant = 0
609
- self.viewGrailPayAbandon.isHidden = true
610
-
611
- if isBankAccountConnected {
612
- self.viewBtnShowSavedCards.isHidden = true
613
- self.btnShowSavedCard.isHidden = true
614
- self.viewBtnShowSavedCardHeight.constant = 0
615
- self.viewBtnShowSavedCardTopCon.constant = 0
640
+ if isSavedNewGrailPayAccount == true {
641
+ updateUIForNewGrailPayAccount()
642
+ } else {
643
+ self.viewBankFields.isHidden = true
644
+ self.viewTermsAndConditions.isHidden = true
645
+ self.grailPayBankLinkView.isHidden = false
646
+ self.btnNext.isHidden = true
647
+ self.btnNextHeight.constant = 0
648
+ self.btnNextTopCon.constant = 0
649
+ self.viewGrailPayAbandon.isHidden = true
650
+
651
+ if isBankAccountConnected {
652
+ self.viewBtnShowSavedCards.isHidden = true
653
+ self.btnShowSavedCard.isHidden = true
654
+ self.viewBtnShowSavedCardHeight.constant = 0
655
+ self.viewBtnShowSavedCardTopCon.constant = 0
656
+ }
616
657
  }
617
658
  }
618
659
  else {
@@ -620,6 +661,11 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
620
661
  self.viewTermsAndConditions.isHidden = false
621
662
  self.grailPayBankLinkView.isHidden = true
622
663
  }
664
+
665
+ // if isFrom == "NewGrailPayAccount" {
666
+ // updateUIForNewGrailPayAccount()
667
+ // }
668
+
623
669
  }
624
670
  else {
625
671
  // self.viewBankFields.isHidden = false
@@ -641,7 +687,7 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
641
687
  self.viewCrypto.isHidden = true
642
688
  self.viewSingleSavedAccount.isHidden = true
643
689
  self.viewChangedAccount.isHidden = true
644
-
690
+
645
691
  if request?.authenticatedACH == false {
646
692
  self.viewBankFields.isHidden = false
647
693
  self.viewTermsAndConditions.isHidden = false
@@ -656,13 +702,20 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
656
702
  }
657
703
  }
658
704
  }
659
-
705
+
660
706
  DispatchQueue.main.async {
661
707
  self.view.setNeedsLayout()
662
708
  self.view.layoutIfNeeded()
663
709
  }
664
710
  }
665
-
711
+
712
+ func updateUIForNewGrailPayAccount() {
713
+ self.viewAddNewGrailPayAccount.isHidden = false
714
+ self.viewAbandonGrailPayNewAccountView.isHidden = false
715
+ self.viewSingleSavedAccount.isHidden = true
716
+ self.grailPayBankLinkView.isHidden = true
717
+ }
718
+
666
719
  //MARK: - Ui Colors Setup.
667
720
  func uiFinishingTouchElements() {
668
721
  // Set background color for the main view
@@ -673,7 +726,7 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
673
726
  viewAccountType.backgroundColor = uiColor
674
727
  tblViewAccountTypes.backgroundColor = uiColor
675
728
  }
676
-
729
+
677
730
  if let bodyBackGroundColor = UserStoreSingleton.shared.body_bg_col,
678
731
  let uiColor = UIColor(hex: bodyBackGroundColor) {
679
732
  viewBtnShowSavedCards.backgroundColor = uiColor
@@ -682,8 +735,11 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
682
735
  viewSingelBankAccount.backgroundColor = uiColor
683
736
  viewGrailPaySavedAccount.backgroundColor = uiColor
684
737
  viewGrailPayAbandon.backgroundColor = uiColor
738
+ settingsView.backgroundColor = uiColor
739
+ viewAbandonGrailPayNewAccountView.backgroundColor = uiColor
740
+ viewGrailPayNewAccountDetail.backgroundColor = uiColor
685
741
  }
686
-
742
+
687
743
  if let primaryBtnBackGroundColor = UserStoreSingleton.shared.primary_btn_bg_col,
688
744
  let uiColor = UIColor(hex: primaryBtnBackGroundColor) {
689
745
  iconShowSavedCard.tintColor = uiColor
@@ -691,6 +747,7 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
691
747
  btnResendOTP.tintColor = uiColor
692
748
  btnSelectSingleSavedCard.tintColor = uiColor
693
749
  btnChangeSavedCard.tintColor = uiColor
750
+ btnChangeNewGrailPayAccountView.tintColor = uiColor
694
751
  btnSelectUpdateCardView.tintColor = uiColor
695
752
  btnCheckSavedAccountForFuture.tintColor = uiColor
696
753
  btnAgreeTermsConditions.tintColor = uiColor
@@ -708,14 +765,19 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
708
765
  btnCheckBoxGrailPayAgreeTerms.tintColor = uiColor
709
766
  lblGrailPayTermsAndCondition.textColor = uiColor
710
767
  lblGrailPayAabandonError.textColor = uiColor
711
-
768
+ btnSelectGrailPayNewAccount.tintColor = uiColor
769
+ btnCheckBoxGrailPayNewAccountSaveAccount.tintColor = uiColor
770
+ btnCheckBoxGrailPayNewAccountTerms.tintColor = uiColor
771
+ lblAbandonGrailPayNewAccountView.textColor = uiColor
772
+ lblTermsAndConditionNewGrailPayAccountView.textColor = uiColor
773
+
712
774
  btnScanCard.tintColor = uiColor // Set color for the image
713
775
  btnScanCard.setTitleColor(uiColor, for: .normal) // Set color for the text
714
776
  if let image = btnScanCard.imageView?.image {
715
777
  let coloredImage = image.withRenderingMode(.alwaysTemplate) // Ensure the image uses tintColor
716
778
  btnScanCard.setImage(coloredImage, for: .normal)
717
779
  }
718
-
780
+
719
781
  txtFieldEmail.tintColor = uiColor
720
782
  txtFieldOTPText1.tintColor = uiColor
721
783
  txtFieldOTPText2.tintColor = uiColor
@@ -727,6 +789,7 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
727
789
  txtFieldAccountType.tintColor = uiColor
728
790
  txtFieldAccountNumber.tintColor = uiColor
729
791
  txtFieldRoutingNumber.tintColor = uiColor
792
+ txtFieldConfirmBankAccount.tintColor = uiColor
730
793
  txtFieldCVVNewCardView.tintColor = uiColor
731
794
  txtFieldCVVUpdateCardView.tintColor = uiColor
732
795
  txtFieldCVVSingleSavedCard.tintColor = uiColor
@@ -743,13 +806,14 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
743
806
  cardExpiryTextField.tintColor = uiColor
744
807
  cardCvvTextField.tintColor = uiColor
745
808
  cardNameTextField.tintColor = uiColor
809
+ txtFieldConfirmAccountNewAccountView.tintColor = uiColor
746
810
  }
747
-
811
+
748
812
  if let secondaryBtnBackgroundColor = UserStoreSingleton.shared.primary_btn_font_col,
749
813
  let secondaryUIColor = UIColor(hex: secondaryBtnBackgroundColor) {
750
814
  btnTryAgain.setTitleColor(secondaryUIColor, for: .normal)
751
815
  }
752
-
816
+
753
817
  if let borderRadiusString = UserStoreSingleton.shared.border_radious,
754
818
  let borderRadius = Double(borderRadiusString) { // Convert String to Double
755
819
  viewBtnShowSavedCards.layer.cornerRadius = CGFloat(borderRadius) // Set corner radius
@@ -763,7 +827,7 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
763
827
  viewCryptoQRCode.layer.cornerRadius = CGFloat(borderRadius)
764
828
  viewGrailPaySavedAccount.layer.cornerRadius = CGFloat(borderRadius)
765
829
  viewGrailPayAbandon.layer.cornerRadius = CGFloat(borderRadius)
766
-
830
+
767
831
  viewTextOTP1.layer.cornerRadius = CGFloat(borderRadius)
768
832
  viewTextOTP2.layer.cornerRadius = CGFloat(borderRadius)
769
833
  viewTextOTP3.layer.cornerRadius = CGFloat(borderRadius)
@@ -792,19 +856,19 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
792
856
  btnTryAgain.layer.masksToBounds = true
793
857
  viewCryptoTryAgain.layer.masksToBounds = true
794
858
  viewCryptoQRCode.layer.masksToBounds = true
795
-
859
+
796
860
  if let secondaryFontColor = UserStoreSingleton.shared.secondary_font_col,
797
861
  let placeholderColor = UIColor(hex: secondaryFontColor) {
798
862
  txtFieldEmail?.attributedPlaceholder = NSAttributedString(
799
863
  string: txtFieldEmail?.placeholder ?? "",
800
864
  attributes: [.foregroundColor: placeholderColor]
801
865
  )
802
-
866
+
803
867
  txtFieldCVVSingleSavedCard?.attributedPlaceholder = NSAttributedString(
804
868
  string: txtFieldCVVSingleSavedCard?.placeholder ?? "",
805
869
  attributes: [.foregroundColor: placeholderColor]
806
870
  )
807
-
871
+
808
872
  lblSaveAccountForFuture.textColor = placeholderColor
809
873
  lblAgreeToThe.textColor = placeholderColor
810
874
  lblAccountTypeSingleAccountView.textColor = placeholderColor
@@ -820,11 +884,15 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
820
884
  btnSettings.tintColor = placeholderColor
821
885
  lblGrailPaySaveAccountForFuture.textColor = placeholderColor
822
886
  lblGrailPayAgreeTo.textColor = placeholderColor
887
+ lblGrailPayNewAccountSaveForFuture.textColor = placeholderColor
888
+ lblIagreeGrailPayNewAccountView.textColor = placeholderColor
823
889
  }
824
-
890
+
825
891
  if let primaryFontColor = UserStoreSingleton.shared.primary_font_col,
826
892
  let uiColor = UIColor(hex: primaryFontColor) {
827
893
  lblChosePaymentMethod.textColor = uiColor
894
+ lblGrailPayNewAccount.tintColor = uiColor
895
+ subLblGrailPayNewAccount.textColor = uiColor
828
896
  lblSavedInfoEmailView.textColor = uiColor
829
897
  subLblSavedEmailInfo.textColor = uiColor
830
898
  lblSavedInfoOTPView.textColor = uiColor
@@ -852,9 +920,11 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
852
920
  lblSetting.textColor = uiColor
853
921
  btnChangeLanguage.tintColor = uiColor
854
922
  btnLogout.tintColor = uiColor
855
- lblGrailPaySavedAccountNumber.tintColor = uiColor
856
- lblGrailPaySavedAccountType.tintColor = uiColor
857
-
923
+ lblGrailPaySavedAccountNumber.textColor = uiColor
924
+ lblGrailPaySavedAccountType.textColor = uiColor
925
+ lblGrailPayNewAccountNumber.textColor = uiColor
926
+ lblGrailPayNewAccountType.textColor = uiColor
927
+
858
928
  txtFieldEmail.textColor = uiColor
859
929
  txtFieldOTPText1.textColor = uiColor
860
930
  txtFieldOTPText2.textColor = uiColor
@@ -863,7 +933,7 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
863
933
  txtFieldOTPText5.textColor = uiColor
864
934
  txtFieldOTPText6.textColor = uiColor
865
935
  }
866
-
936
+
867
937
  if let viewBorderColor = UserStoreSingleton.shared.secondary_font_col,
868
938
  let borderColor = UIColor(hex: viewBorderColor) {
869
939
  viewTextFieldCardNumber.layer.borderColor = borderColor.cgColor
@@ -881,12 +951,12 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
881
951
  viewCryptoQRCode.layer.borderColor = UIColor.systemGray.cgColor
882
952
  viewCryptoQRCode.layer.borderWidth = 1.0
883
953
  }
884
-
954
+
885
955
  if let viewOtpPrimaryBackgroundColor = UserStoreSingleton.shared.primary_btn_bg_col,
886
956
  let primaryUIColor = UIColor(hex: viewOtpPrimaryBackgroundColor),
887
957
  let viewOtpSecondaryColor = UserStoreSingleton.shared.secondary_font_col,
888
958
  let secondaryUIColor = UIColor(hex: viewOtpSecondaryColor) {
889
-
959
+
890
960
  viewTextOTP1.layer.borderWidth = 1.0
891
961
  viewTextOTP1.layer.borderColor = txtFieldOTPText1.text?.isEmpty == false ? primaryUIColor.cgColor : secondaryUIColor.cgColor
892
962
  viewTextOTP2.layer.borderWidth = 1.0
@@ -900,7 +970,7 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
900
970
  viewTextOTP6.layer.borderWidth = 1.0
901
971
  viewTextOTP6.layer.borderColor = txtFieldOTPText6.text?.isEmpty == false ? primaryUIColor.cgColor : secondaryUIColor.cgColor
902
972
  }
903
-
973
+
904
974
  if let fontSizeString = UserStoreSingleton.shared.fontSize,
905
975
  let fontSizeDouble = Double(fontSizeString) { // Convert String to Double
906
976
  let fontSize = CGFloat(fontSizeDouble) // Convert Double to CGFloat
@@ -937,9 +1007,17 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
937
1007
  lblGrailPayTermsAndCondition.font = UIFont.systemFont(ofSize: fontSize)
938
1008
  lblGrailPayAgreeTo.font = UIFont.systemFont(ofSize: fontSize)
939
1009
  lblGrailPaySaveAccountForFuture.font = UIFont.systemFont(ofSize: fontSize)
1010
+ lblIagreeGrailPayNewAccountView.font = UIFont.systemFont(ofSize: fontSize)
1011
+ lblGrailPayNewAccountSaveForFuture.font = UIFont.systemFont(ofSize: fontSize)
1012
+ lblTermsAndConditionNewGrailPayAccountView.font = UIFont.systemFont(ofSize: fontSize)
1013
+ subLblGrailPayNewAccount.font = UIFont.systemFont(ofSize: fontSize)
1014
+ lblAbandonGrailPayNewAccountView.font = UIFont.systemFont(ofSize: fontSize)
1015
+ lblGrailPayNewAccountNumber.font = UIFont.systemFont(ofSize: fontSize)
1016
+ lblGrailPayNewAccountType.font = UIFont.systemFont(ofSize: fontSize)
1017
+
940
1018
  lblGrailPaySavedAccountType.font = UIFont.systemFont(ofSize: fontSize)
941
1019
  lblGrailPaySavedAccountNumber.font = UIFont.systemFont(ofSize: fontSize)
942
-
1020
+
943
1021
  btnShowSavedCard.titleLabel?.font = UIFont.systemFont(ofSize: fontSize)
944
1022
  btnResendOTP.titleLabel?.font = UIFont.systemFont(ofSize: fontSize)
945
1023
  btnChangeSavedCard.titleLabel?.font = UIFont.systemFont(ofSize: fontSize)
@@ -956,61 +1034,62 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
956
1034
  btnSavedCardForFutureNewCardView.titleLabel?.font = UIFont.systemFont(ofSize: fontSize)
957
1035
  }
958
1036
  }
959
-
1037
+
960
1038
  func setUpTextFieldsDelegates() {
961
1039
  cardNumberTextField.textField.delegate = self
962
1040
  cardExpiryTextField.textField.delegate = self
963
1041
  cardCvvTextField.textField.delegate = self
964
1042
  cardNameTextField.textField.delegate = self
965
-
1043
+
966
1044
  txtFieldEmail.delegate = self
967
-
1045
+
968
1046
  txtFieldCVVSingleSavedCard.delegate = self
969
1047
  txtFieldExpireDateUpdateCardView.delegate = self
970
1048
  txtFieldCVVUpdateCardView.delegate = self
971
1049
  txtFieldNameOnCardUpdateCardView.delegate = self
972
-
1050
+
973
1051
  txtFieldCardNumberNewCardView.delegate = self
974
1052
  txtFieldExpiryDateNewCardView.delegate = self
975
1053
  txtFieldCVVNewCardView.delegate = self
976
1054
  txtFieldNameOnCardNewCardView.delegate = self
977
-
1055
+
978
1056
  txtFieldAccountName.delegate = self
979
1057
  txtFieldRoutingNumber.delegate = self
980
1058
  txtFieldAccountType.delegate = self
981
1059
  txtFieldAccountNumber.delegate = self
982
-
1060
+
983
1061
  txtFieldAccountNameNewAccountView.delegate = self
984
1062
  txtFieldRoutingNumberNewAccountView.delegate = self
985
1063
  txtFieldAccountTypeNewAccountView.delegate = self
986
1064
  txtFieldAccountNumber.delegate = self
1065
+ txtFieldConfirmBankAccount.delegate = self
987
1066
  }
988
-
1067
+
989
1068
  private func updateSaveButtons() {
990
1069
  let isSavedCard = request?.saveCard ?? false
991
1070
  let isSavedAccount = request?.saveAccount ?? false
992
-
1071
+
993
1072
  let cardImageName = isSavedCard ? "checkmark.square.fill" : "square"
994
1073
  let accountImageName = isSavedAccount ? "checkmark.square.fill" : "square"
995
-
1074
+
996
1075
  btnCheckBoxSavedCard.setImage(UIImage(systemName: cardImageName), for: .normal)
997
1076
  btnSavedCardForFutureNewCardView.setImage(UIImage(systemName: cardImageName), for: .normal)
998
-
1077
+
999
1078
  btnCheckSavedAccountForFuture.setImage(UIImage(systemName: accountImageName), for: .normal)
1000
1079
  btnSavedNewAccountForFuture.setImage(UIImage(systemName: accountImageName), for: .normal)
1001
1080
  }
1002
-
1081
+
1003
1082
  //MARK: - Term & Conditions setup for normal bank fields view
1004
1083
  func setUpTermAndConditionsButton() {
1005
1084
  let string = "TERMS & CONDITIONS"
1006
1085
  let attributedString = NSMutableAttributedString(string: string)
1007
-
1086
+
1008
1087
  attributedString.addAttribute(
1009
1088
  .font,
1010
1089
  value: UIFont.systemFont(ofSize: 12, weight: .medium),
1011
1090
  range: NSRange(location: 0, length: string.count)
1012
1091
  )
1013
-
1092
+
1014
1093
  // Assign the attributed string to the label
1015
1094
  lblTermsAndConditions.attributedText = attributedString
1016
1095
  lblTermsAndConditions.isUserInteractionEnabled = true
@@ -1018,7 +1097,7 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
1018
1097
  let tap = UITapGestureRecognizer(target: self, action: #selector(aboutTerms))
1019
1098
  lblTermsAndConditions.addGestureRecognizer(tap)
1020
1099
  }
1021
-
1100
+
1022
1101
  @objc func aboutTerms(sender: UITapGestureRecognizer) {
1023
1102
  guard let vc = UIStoryboard(name: "easymerchantsdk", bundle: Bundle.easyPayBundle).instantiateViewController(withIdentifier: "TermAndConditionsVC") as? TermAndConditionsVC else {
1024
1103
  print("Error: Could not find TermAndConditionsVC in easymerchantsdk storyboard.")
@@ -1027,18 +1106,18 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
1027
1106
  vc.modalPresentationStyle = .overFullScreen
1028
1107
  present(vc, animated: true, completion: nil)
1029
1108
  }
1030
-
1109
+
1031
1110
  //MARK: - Term & Conditions setup for Single saved bank account view
1032
1111
  func setUpTermAndConditionsForSingleSavedAccountButton() {
1033
1112
  let string = "TERMS & CONDITIONS"
1034
1113
  let attributedString = NSMutableAttributedString(string: string)
1035
-
1114
+
1036
1115
  attributedString.addAttribute(
1037
1116
  .font,
1038
1117
  value: UIFont.systemFont(ofSize: 12, weight: .medium),
1039
1118
  range: NSRange(location: 0, length: string.count)
1040
1119
  )
1041
-
1120
+
1042
1121
  // Assign the attributed string to the label
1043
1122
  lblTermsAndCondtionsSingleAccount.attributedText = attributedString
1044
1123
  lblTermsAndCondtionsSingleAccount.isUserInteractionEnabled = true
@@ -1046,7 +1125,7 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
1046
1125
  let tap = UITapGestureRecognizer(target: self, action: #selector(aboutTermsAndConditions))
1047
1126
  lblTermsAndCondtionsSingleAccount.addGestureRecognizer(tap)
1048
1127
  }
1049
-
1128
+
1050
1129
  @objc func aboutTermsAndConditions(sender: UITapGestureRecognizer) {
1051
1130
  guard let vc = UIStoryboard(name: "easymerchantsdk", bundle: Bundle.easyPayBundle).instantiateViewController(withIdentifier: "TermAndConditionsVC") as? TermAndConditionsVC else {
1052
1131
  print("Error: Could not find TermAndConditionsVC in easymerchantsdk storyboard.")
@@ -1055,18 +1134,18 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
1055
1134
  vc.modalPresentationStyle = .overFullScreen
1056
1135
  present(vc, animated: true, completion: nil)
1057
1136
  }
1058
-
1137
+
1059
1138
  //MARK: - Term & Conditions setup for GrailPay saved bank account view
1060
1139
  func setUpTermAndConditionsForGrailPaySavedAccountButton() {
1061
1140
  let string = "TERMS & CONDITIONS"
1062
1141
  let attributedString = NSMutableAttributedString(string: string)
1063
-
1142
+
1064
1143
  attributedString.addAttribute(
1065
1144
  .font,
1066
1145
  value: UIFont.systemFont(ofSize: 12, weight: .medium),
1067
1146
  range: NSRange(location: 0, length: string.count)
1068
1147
  )
1069
-
1148
+
1070
1149
  // Assign the attributed string to the label
1071
1150
  lblGrailPayTermsAndCondition.attributedText = attributedString
1072
1151
  lblGrailPayTermsAndCondition.isUserInteractionEnabled = true
@@ -1074,7 +1153,7 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
1074
1153
  let tap = UITapGestureRecognizer(target: self, action: #selector(aboutTermsAndGrailPayConditions))
1075
1154
  lblGrailPayTermsAndCondition.addGestureRecognizer(tap)
1076
1155
  }
1077
-
1156
+
1078
1157
  @objc func aboutTermsAndGrailPayConditions(sender: UITapGestureRecognizer) {
1079
1158
  guard let vc = UIStoryboard(name: "easymerchantsdk", bundle: Bundle.easyPayBundle).instantiateViewController(withIdentifier: "TermAndConditionsVC") as? TermAndConditionsVC else {
1080
1159
  print("Error: Could not find TermAndConditionsVC in easymerchantsdk storyboard.")
@@ -1083,12 +1162,12 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
1083
1162
  vc.modalPresentationStyle = .overFullScreen
1084
1163
  present(vc, animated: true, completion: nil)
1085
1164
  }
1086
-
1165
+
1087
1166
  func didPassTextBack(_ text: String) {
1088
1167
  print("Received text: \(text)")
1089
1168
  isFrom = text
1090
1169
  }
1091
-
1170
+
1092
1171
  override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
1093
1172
  guard keyPath == "contentSize" else {
1094
1173
  super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
@@ -1102,42 +1181,42 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
1102
1181
  else if let tableView = object as? UITableView, tableView == tblViewSavedBankAccounts {
1103
1182
  tblViewSavedBankAccountsHeight.constant = tblViewSavedBankAccounts.contentSize.height
1104
1183
  }
1105
-
1184
+
1106
1185
  UIView.animate(withDuration: 0.5) {
1107
1186
  self.updateViewConstraints()
1108
1187
  }
1109
1188
  }
1110
-
1189
+
1111
1190
  private func setupTextFields() {
1112
1191
  let textFields = [txtFieldOTPText1, txtFieldOTPText2, txtFieldOTPText3, txtFieldOTPText4, txtFieldOTPText5, txtFieldOTPText6]
1113
-
1192
+
1114
1193
  for textField in textFields {
1115
1194
  textField?.delegate = self
1116
1195
  textField?.textContentType = .oneTimeCode
1117
1196
  textField?.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged)
1118
1197
  }
1119
1198
  }
1120
-
1199
+
1121
1200
  func configureWith(request: Request, delegate: EasyPayViewControllerDelegate?) {
1122
1201
  self.request = request
1123
1202
  self.delegate = delegate
1124
1203
  }
1125
-
1204
+
1126
1205
  @objc func dismissKeyboard() {
1127
1206
  self.view.endEditing(true)
1128
1207
  }
1129
-
1208
+
1130
1209
  private func startTimer() {
1131
1210
  timeRemaining = 180 // 3 minutes
1132
1211
  lblOtpTimer.text = formatTime(timeRemaining)
1133
1212
  timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(updateTimer), userInfo: nil, repeats: true)
1134
1213
  }
1135
-
1214
+
1136
1215
  private func stopTimer() {
1137
1216
  timer?.invalidate()
1138
1217
  timer = nil
1139
1218
  }
1140
-
1219
+
1141
1220
  @objc private func updateTimer() {
1142
1221
  if timeRemaining > 0 {
1143
1222
  timeRemaining -= 1
@@ -1150,68 +1229,68 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
1150
1229
  lblUntillResendOtp.isHidden = true
1151
1230
  }
1152
1231
  }
1153
-
1232
+
1154
1233
  private func formatTime(_ seconds: Int) -> String {
1155
1234
  let minutes = seconds / 60
1156
1235
  let seconds = seconds % 60
1157
1236
  return String(format: "%02d:%02d", minutes, seconds)
1158
1237
  }
1159
-
1238
+
1160
1239
  //MARK: - Crypto Functions
1161
1240
  private func generateQRCode(from string: String) -> UIImage? {
1162
1241
  // Convert string to data
1163
1242
  guard let data = string.data(using: .ascii) else { return nil }
1164
-
1243
+
1165
1244
  // Create QR Code filter
1166
1245
  guard let qrFilter = CIFilter(name: "CIQRCodeGenerator") else { return nil }
1167
1246
  qrFilter.setValue(data, forKey: "inputMessage")
1168
-
1247
+
1169
1248
  // Get QR Code output image
1170
1249
  guard let qrImage = qrFilter.outputImage else { return nil }
1171
-
1250
+
1172
1251
  // Scale QR Code for better quality
1173
1252
  let transform = CGAffineTransform(scaleX: 10, y: 10)
1174
1253
  let scaledQRImage = qrImage.transformed(by: transform)
1175
-
1254
+
1176
1255
  // Get the primary font color from UserStoreSingleton
1177
1256
  guard let primaryFontHex = UserStoreSingleton.shared.primary_font_col else {
1178
1257
  return UIImage(ciImage: scaledQRImage) // Return default black QR
1179
1258
  }
1180
-
1259
+
1181
1260
  guard let primaryUIColor = UIColor(hex: primaryFontHex) else {
1182
1261
  return UIImage(ciImage: scaledQRImage)
1183
1262
  }
1184
-
1263
+
1185
1264
  // Convert UIColor to CIColor
1186
1265
  let primaryCIColor = CIColor(color: primaryUIColor)
1187
1266
  let backgroundCIColor = CIColor(color: .clear) // Transparent background
1188
-
1267
+
1189
1268
  // Apply color filter to QR code
1190
1269
  guard let colorFilter = CIFilter(name: "CIFalseColor") else { return nil }
1191
1270
  colorFilter.setValue(scaledQRImage, forKey: "inputImage")
1192
1271
  colorFilter.setValue(primaryCIColor, forKey: "inputColor0") // QR Code color
1193
1272
  colorFilter.setValue(backgroundCIColor, forKey: "inputColor1") // Background color
1194
-
1273
+
1195
1274
  // Get the colored QR output
1196
1275
  guard let coloredQRImage = colorFilter.outputImage else { return nil }
1197
-
1276
+
1198
1277
  // Convert CIImage to UIImage
1199
1278
  let context = CIContext()
1200
1279
  guard let cgImage = context.createCGImage(coloredQRImage, from: coloredQRImage.extent) else { return nil }
1201
-
1280
+
1202
1281
  return UIImage(cgImage: cgImage)
1203
1282
  }
1204
-
1283
+
1205
1284
  func startCryptoTimer() {
1206
1285
  resetTimer()
1207
-
1286
+
1208
1287
  lblTimerCrypto.text = formatTime(seconds: totalTimeInSeconds)
1209
-
1288
+
1210
1289
  cryptoTimer = DispatchSource.makeTimerSource()
1211
1290
  cryptoTimer?.schedule(deadline: .now(), repeating: 1.0)
1212
1291
  cryptoTimer?.setEventHandler { [weak self] in
1213
1292
  guard let self = self else { return }
1214
-
1293
+
1215
1294
  DispatchQueue.main.async {
1216
1295
  if self.totalTimeInSeconds > 0 {
1217
1296
  self.totalTimeInSeconds -= 1
@@ -1226,30 +1305,30 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
1226
1305
  }
1227
1306
  }
1228
1307
  }
1229
-
1308
+
1230
1309
  cryptoTimer?.resume()
1231
1310
  isTimerRunning = true
1232
-
1311
+
1233
1312
  // Start the 30-second interval timer
1234
1313
  startCryptoInfoTimer()
1235
1314
  }
1236
-
1315
+
1237
1316
  func resetTimer() {
1238
1317
  cryptoTimer?.cancel()
1239
1318
  cryptoTimer = nil
1240
1319
  totalTimeInSeconds = 300
1241
1320
  isTimerRunning = false
1242
-
1321
+
1243
1322
  // Stop the 30-second interval timer
1244
1323
  stopCryptoInfoTimer()
1245
1324
  }
1246
-
1325
+
1247
1326
  func formatTime(seconds: Int) -> String {
1248
1327
  let minutes = seconds / 60
1249
1328
  let remainingSeconds = seconds % 60
1250
1329
  return String(format: "%02d:%02d", minutes, remainingSeconds)
1251
1330
  }
1252
-
1331
+
1253
1332
  func startCryptoInfoTimer() {
1254
1333
  // Invalidate any existing timer
1255
1334
  cryptoInfoTimer?.invalidate()
@@ -1261,43 +1340,43 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
1261
1340
  }
1262
1341
  }
1263
1342
  }
1264
-
1343
+
1265
1344
  func stopCryptoInfoTimer() {
1266
1345
  cryptoInfoTimer?.invalidate()
1267
1346
  cryptoInfoTimer = nil
1268
1347
  }
1269
-
1348
+
1270
1349
  @IBAction func actionBtnClose(_ sender: UIButton) {
1271
1350
  UserStoreSingleton.shared.clearUserData()
1272
1351
  UserStoreSingleton.shared.isLoggedIn = false
1273
1352
  self.dismiss(animated: true)
1274
1353
  }
1275
-
1354
+
1276
1355
  @IBAction func actionBtnShowSavedCards(_ sender: UIButton) {
1277
1356
  emailView.isHidden = false
1278
1357
  OTPView.isHidden = true
1279
1358
  }
1280
-
1359
+
1281
1360
  @IBAction func actionBtnCloseEmailView(_ sender: UIButton) {
1282
1361
  emailView.isHidden = true
1283
1362
  }
1284
-
1363
+
1285
1364
  @IBAction func actionBtnCloseOTPView(_ sender: UIButton) {
1286
1365
  stopTimer()
1287
1366
  OTPView.isHidden = true
1288
1367
  }
1289
-
1368
+
1290
1369
  @IBAction func actionBtnResendOTP(_ sender: UIButton) {
1291
1370
  startTimer()
1292
1371
  btnResendOTP.isHidden = true
1293
1372
  imgEsclamationMark.isHidden = false
1294
1373
  lblOtpTimer.isHidden = false
1295
1374
  lblUntillResendOtp.isHidden = false
1296
-
1375
+
1297
1376
  //Call email verification APi
1298
1377
  emailVerificationApi()
1299
1378
  }
1300
-
1379
+
1301
1380
  @IBAction func actionBtnSelectSingleSavedCard(_ sender: UIButton) {
1302
1381
  // Toggle the boolean property
1303
1382
  isSelectForPay.toggle()
@@ -1325,7 +1404,7 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
1325
1404
  btnNextTopCon.constant = 8
1326
1405
  }
1327
1406
  }
1328
-
1407
+
1329
1408
  @IBAction func actionBtnChangeSavedCard(_ sender: UIButton) {
1330
1409
  self.viewChangeCard.isHidden = false
1331
1410
  self.viewCardFields.isHidden = true
@@ -1336,7 +1415,7 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
1336
1415
  self.viewUpdateCard.isHidden = true
1337
1416
  self.viewAddNewCard.isHidden = true
1338
1417
  }
1339
-
1418
+
1340
1419
  @IBAction func actionBtnCloseChangedCardView(_ sender: UIButton) {
1341
1420
  self.viewChangeCard.isHidden = true
1342
1421
  self.viewSingleSavedCard.isHidden = false
@@ -1347,45 +1426,45 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
1347
1426
  self.viewUpdateCard.isHidden = true
1348
1427
  self.viewAddNewCard.isHidden = true
1349
1428
  }
1350
-
1429
+
1351
1430
  @IBAction func actionBtnPayNowSingleSavedCard(_ sender: UIButton) {
1352
1431
  // Check if billingInfoData is not nil or empty
1353
1432
  if let billingInfoData = request.billingInfoData,
1354
1433
  let json = try? JSONSerialization.jsonObject(with: billingInfoData, options: []),
1355
1434
  let jsonDict = json as? [String: Any], !jsonDict.isEmpty {
1356
1435
  print("Billing Info Data: \(jsonDict)")
1357
-
1436
+
1358
1437
  let cvvText = txtFieldCVVSingleSavedCard.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
1359
-
1438
+
1360
1439
  if cvvText.isEmpty {
1361
1440
  self.showAlert(title: "Missing CVV", message: "Please enter the CVV to proceed.")
1362
1441
  } else if cvvText.count < 3 {
1363
1442
  self.showAlert(title: "Invalid CVV", message: "CVV must be at least 3 digits.")
1364
1443
  }
1365
-
1444
+
1366
1445
  // Instantiate BillingInfoVC and pass the selected card data and CVV text
1367
1446
  let billingInfoVC = UIStoryboard(name: "easymerchantsdk", bundle: .easyPayBundle).instantiateViewController(withIdentifier: "BillingInfoVC") as! BillingInfoVC
1368
-
1447
+
1369
1448
  billingInfoVC.isFrom = "SavedCards"
1370
1449
  billingInfoVC.selectedCard = selectedCard // Passing the selected card data
1371
1450
  billingInfoVC.cvvText = cvvText // Passing the CVV text
1372
1451
  billingInfoVC.amount = Int(request.amount)
1373
1452
  billingInfoVC.selectedPaymentMethod = selectedPaymentMethod
1374
-
1453
+
1375
1454
  // Pass the billing info data and auth token
1376
1455
  if let billingInfoData = request.billingInfoData,
1377
1456
  let json = try? JSONSerialization.jsonObject(with: billingInfoData, options: []),
1378
1457
  let jsonDict = json as? [String: Any] {
1379
1458
  billingInfoVC.billingInfoData = jsonDict
1380
1459
  }
1381
-
1460
+
1382
1461
  // Navigate to BillingInfoVC
1383
1462
  self.navigationController?.pushViewController(billingInfoVC, animated: true)
1384
1463
  }
1385
1464
  else {
1386
1465
  // If billingInfoData is nil or empty, set the button title to "Pay Now"
1387
1466
  let cvvText = txtFieldCVVSingleSavedCard.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
1388
-
1467
+
1389
1468
  if cvvText.isEmpty {
1390
1469
  self.showAlert(title: "Missing CVV", message: "Please enter the CVV to proceed.")
1391
1470
  }
@@ -1397,11 +1476,11 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
1397
1476
  }
1398
1477
  }
1399
1478
  }
1400
-
1479
+
1401
1480
  @IBAction func actionBtnSelectUpdateCardView(_ sender: UIButton) {
1402
-
1481
+
1403
1482
  }
1404
-
1483
+
1405
1484
  @IBAction func actionBtnUpdateCard(_ sender: UIButton) {
1406
1485
  // Check if a card ID is selected
1407
1486
  guard let cardID = selectedCardID else {
@@ -1410,11 +1489,11 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
1410
1489
  self.present(alert, animated: true, completion: nil)
1411
1490
  return
1412
1491
  }
1413
-
1492
+
1414
1493
  // Call the updateCardApi with the selected card's ID
1415
1494
  updateCardApi(cardID: cardID)
1416
1495
  }
1417
-
1496
+
1418
1497
  @IBAction func actionBtnCloseUpdateCardView(_ sender: UIButton) {
1419
1498
  self.viewChangeCard.isHidden = false
1420
1499
  self.viewSingleSavedCard.isHidden = true
@@ -1425,7 +1504,7 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
1425
1504
  self.viewUpdateCard.isHidden = true
1426
1505
  self.viewAddNewCard.isHidden = true
1427
1506
  }
1428
-
1507
+
1429
1508
  @IBAction func actionBtnCloseNewCardView(_ sender: UIButton) {
1430
1509
  self.viewChangeCard.isHidden = false
1431
1510
  self.viewSingleSavedCard.isHidden = true
@@ -1436,7 +1515,7 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
1436
1515
  self.viewUpdateCard.isHidden = true
1437
1516
  self.viewAddNewCard.isHidden = true
1438
1517
  }
1439
-
1518
+
1440
1519
  @IBAction func actionBtnPayNowNewCardView(_ sender: UIButton) {
1441
1520
  if UserStoreSingleton.shared.isLoggedIn == true {
1442
1521
  //if billing info is availbale
@@ -1444,13 +1523,13 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
1444
1523
  let json = try? JSONSerialization.jsonObject(with: billingInfoData, options: []),
1445
1524
  let jsonDict = json as? [String: Any], !jsonDict.isEmpty {
1446
1525
  print("Billing Info Data: \(jsonDict)")
1447
-
1526
+
1448
1527
  // Retrieve and trim text field values (removing leading and trailing whitespace)
1449
1528
  let cardNumber = txtFieldCardNumberNewCardView.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
1450
1529
  let expiryDate = txtFieldExpiryDateNewCardView.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
1451
1530
  let cvv = txtFieldCVVNewCardView.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
1452
1531
  let nameOnCard = txtFieldNameOnCardNewCardView.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
1453
-
1532
+
1454
1533
  // Check if any field is empty
1455
1534
  if cardNumber.isEmpty || expiryDate.isEmpty || cvv.isEmpty || nameOnCard.isEmpty {
1456
1535
  let alert = UIAlertController(title: "Missing Information", message: "Please fill in all card details.", preferredStyle: .alert)
@@ -1458,13 +1537,13 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
1458
1537
  self.present(alert, animated: true, completion: nil)
1459
1538
  return
1460
1539
  }
1461
-
1540
+
1462
1541
  // Validate expiry date format
1463
1542
  let expiryDateFormat = "MM/yyyy"
1464
1543
  let dateFormatter = DateFormatter()
1465
1544
  dateFormatter.dateFormat = expiryDateFormat
1466
1545
  dateFormatter.locale = Locale(identifier: "en_US_POSIX")
1467
-
1546
+
1468
1547
  // Split the expiry date and validate its format
1469
1548
  let exp = expiryDate.split(separator: "/")
1470
1549
  if exp.count != 2 || exp[0].count != 2 || exp[1].count != 4 {
@@ -1473,7 +1552,7 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
1473
1552
  self.present(alert, animated: true, completion: nil)
1474
1553
  return
1475
1554
  }
1476
-
1555
+
1477
1556
  // Check if the expiry date is valid
1478
1557
  if let expiryDateObj = dateFormatter.date(from: expiryDate) {
1479
1558
  // Check if the expiry date is in the past
@@ -1492,28 +1571,28 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
1492
1571
  self.present(alert, animated: true, completion: nil)
1493
1572
  return
1494
1573
  }
1495
-
1574
+
1496
1575
  // Instantiate BillingInfoVC and pass the card details
1497
1576
  let billingInfoVC = UIStoryboard(name: "easymerchantsdk", bundle: .easyPayBundle).instantiateViewController(withIdentifier: "BillingInfoVC") as! BillingInfoVC
1498
-
1577
+
1499
1578
  // Pass the card details
1500
1579
  billingInfoVC.cardNumber = cardNumber
1501
1580
  billingInfoVC.expiryDate = expiryDate
1502
1581
  billingInfoVC.cvv = cvv
1503
1582
  billingInfoVC.nameOnCard = nameOnCard
1504
1583
  billingInfoVC.isSavedNewCard = isSavedNewCardForFuture
1505
-
1584
+
1506
1585
  billingInfoVC.isFrom = "AddNewCard"
1507
1586
  billingInfoVC.amount = Int(request.amount)
1508
1587
  billingInfoVC.selectedPaymentMethod = selectedPaymentMethod
1509
-
1588
+
1510
1589
  // Pass the billing info data and auth token
1511
1590
  if let billingInfoData = request.billingInfoData,
1512
1591
  let json = try? JSONSerialization.jsonObject(with: billingInfoData, options: []),
1513
1592
  let jsonDict = json as? [String: Any] {
1514
1593
  billingInfoVC.billingInfoData = jsonDict
1515
1594
  }
1516
-
1595
+
1517
1596
  // Navigate to BillingInfoVC
1518
1597
  self.navigationController?.pushViewController(billingInfoVC, animated: true)
1519
1598
  }
@@ -1522,68 +1601,81 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
1522
1601
  }
1523
1602
  }
1524
1603
  }
1525
-
1604
+
1526
1605
  @IBAction func actionBtnSaveCardForFutureNewCardView(_ sender: UIButton) {
1527
1606
  isSavedNewCardForFuture.toggle()
1528
1607
  // Change the image based on the state
1529
1608
  let imageName = isSavedNewCardForFuture ? "checkmark.square.fill" : "square"
1530
1609
  btnSavedCardForFutureNewCardView.setImage(UIImage(systemName: imageName), for: .normal)
1531
1610
  }
1532
-
1611
+
1533
1612
  @IBAction func actionBtnSaveCardForFuture(_ sender: UIButton) {
1534
1613
  isSavedForFuture.toggle()
1535
1614
  // Change the image based on the state
1536
1615
  let imageName = isSavedForFuture ? "checkmark.square.fill" : "square"
1537
1616
  btnCheckBoxSavedCard.setImage(UIImage(systemName: imageName), for: .normal)
1538
1617
  }
1539
-
1618
+
1540
1619
  @IBAction func actionBtnGrailPayCheckBoxSaveAccountForFuture(_ sender: UIButton) {
1541
-
1620
+
1542
1621
  }
1543
-
1622
+
1544
1623
  @IBAction func actionBtnGrailPayCheckAgreeTerms(_ sender: UIButton) {
1545
-
1624
+
1625
+ }
1626
+
1627
+ @IBAction func actinBtnChangeGrailPayNewAccount(_ sender: UIButton) {
1628
+
1546
1629
  }
1547
-
1630
+
1631
+ @IBAction func actionBtnCheckBoxGrailPayNewAccountSavedForFuture(_ sender: UIButton) {
1632
+
1633
+ }
1634
+
1635
+ @IBAction func actionBtnCheckBoxTermsGrailPayNewAccount(_ sender: UIButton) {
1636
+
1637
+ }
1638
+
1548
1639
  @IBAction private func actionBtnLinkGrailPayBankAccount(_ sender: UIButton) {
1549
1640
  if isBankAccountConnected {
1550
- print("Proceed with bank-connected actions.")
1551
- } else {
1641
+ print("Proceed payment with GrailPay Bank Account")
1642
+ }
1643
+ else {
1552
1644
  guard let grailPayParams = request.grailPayParams else {
1553
1645
  print("GrailPay parameters not available in request")
1554
1646
  return
1555
1647
  }
1556
-
1648
+
1557
1649
  GrailPayHelper.presentGrailPay(from: self, request: grailPayParams) { [weak self] result in
1558
1650
  guard let self = self else { return }
1559
-
1651
+
1560
1652
  switch result.type {
1561
1653
  case .success:
1562
1654
  print("📦 Raw chargeData type: \(type(of: result.chargeData)) — value: \(String(describing: result.chargeData))")
1563
-
1655
+
1564
1656
  // Safely unwrap the chargeData dictionary
1565
1657
  if let chargeData = result.chargeData,
1566
1658
  let dataArray = chargeData["data"] as? [[String: Any]],
1567
1659
  let firstAccount = dataArray.first {
1568
-
1660
+
1569
1661
  print("✅ Bank connected: \(firstAccount)")
1570
1662
  // 🚀 Call Account Connect API
1571
1663
  self.accountConnectApi(account: firstAccount)
1572
1664
  // ✅ Optional UI updates
1573
1665
  self.lblGrailPayAabandonError.text = "Default account successfully set"
1574
1666
  self.viewGrailPayAbandon.isHidden = false
1575
-
1667
+
1576
1668
  if selectedPaymentMethod == "Bank" {
1577
1669
  self.viewBtnShowSavedCards.isHidden = true
1578
1670
  }
1579
-
1671
+
1580
1672
  let amountText = String(format: "$%.2f", request.amount)
1581
1673
  let submitText = request.submitButtonText
1582
-
1674
+
1583
1675
  let defaultTitle = (submitText?.isEmpty == false)
1584
1676
  ? "\(submitText!) (\(amountText))"
1585
1677
  : "Pay Now (\(amountText))"
1586
-
1678
+
1587
1679
  btnLinkBankAccount.setTitle(defaultTitle, for: .normal)
1588
1680
  } else {
1589
1681
  print("⚠️ chargeData does not contain valid account data")
@@ -1605,30 +1697,90 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
1605
1697
  }
1606
1698
  }
1607
1699
  }
1608
-
1700
+
1701
+ @IBAction func actionLinkNewGrailPayAccount(_ sender: UIButton) {
1702
+ if isBankAccountConnected {
1703
+ print("Proceed payment with GrailPay Bank Account")
1704
+ }
1705
+ guard let grailPayParams = request.grailPayParams else {
1706
+ print("GrailPay parameters not available in request")
1707
+ return
1708
+ }
1709
+
1710
+ GrailPayHelper.presentGrailPay(from: self, request: grailPayParams) { [weak self] result in
1711
+ guard let self = self else { return }
1712
+
1713
+ switch result.type {
1714
+ case .success:
1715
+ print("📦 Raw chargeData type: \(type(of: result.chargeData)) — value: \(String(describing: result.chargeData))")
1716
+
1717
+ // Safely unwrap the chargeData dictionary
1718
+ if let chargeData = result.chargeData,
1719
+ let dataArray = chargeData["data"] as? [[String: Any]],
1720
+ let firstAccount = dataArray.first {
1721
+
1722
+ print("✅ Bank connected: \(firstAccount)")
1723
+ // 🚀 Call Account Connect API
1724
+ self.newGrailPayAccountConnectApi(account: firstAccount)
1725
+ // ✅ Optional UI updates
1726
+ self.lblAbandonGrailPayNewAccountView.text = "Default account successfully set"
1727
+ self.viewAbandonGrailPayNewAccountView.isHidden = false
1728
+
1729
+ if selectedPaymentMethod == "Bank" {
1730
+ self.viewBtnShowSavedCards.isHidden = true
1731
+ }
1732
+
1733
+ let amountText = String(format: "$%.2f", request.amount)
1734
+ let submitText = request.submitButtonText
1735
+
1736
+ let defaultTitle = (submitText?.isEmpty == false)
1737
+ ? "\(submitText!) (\(amountText))"
1738
+ : "Pay Now (\(amountText))"
1739
+
1740
+ btnLinkGrailPayAddNewAccount.setTitle(defaultTitle, for: .normal)
1741
+ } else {
1742
+ print("⚠️ chargeData does not contain valid account data")
1743
+ self.lblAbandonGrailPayNewAccountView.text = "Failed to retrieve account info"
1744
+ self.lblAbandonGrailPayNewAccountView.textColor = .systemRed
1745
+ self.viewAbandonGrailPayNewAccountView.isHidden = false
1746
+ }
1747
+ case .cancelled:
1748
+ print("⚠️ GrailPay cancelled")
1749
+ self.viewAbandonGrailPayNewAccountView.isHidden = false
1750
+ self.lblAbandonGrailPayNewAccountView.text = "User has abandoned the action"
1751
+ case .error:
1752
+ if let error = result.error {
1753
+ print("❌ GrailPay error: \(error.localizedDescription)")
1754
+ } else {
1755
+ print("❌ GrailPay unknown error")
1756
+ }
1757
+ }
1758
+ }
1759
+ }
1760
+
1609
1761
  //MARK: - Account Connect Api
1610
1762
  func accountConnectApi(account: [String: Any]) {
1611
1763
  showLoadingIndicator()
1612
-
1764
+
1613
1765
  let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.accountConnect.path()
1614
-
1766
+
1615
1767
  guard let serviceURL = URL(string: fullURL) else {
1616
1768
  print("Invalid URL")
1617
1769
  hideLoadingIndicator()
1618
1770
  return
1619
1771
  }
1620
-
1772
+
1621
1773
  var request = URLRequest(url: serviceURL)
1622
1774
  request.httpMethod = "POST"
1623
1775
  request.addValue("application/json", forHTTPHeaderField: "Content-Type")
1624
-
1776
+
1625
1777
  // Add API headers
1626
1778
  if let apiKey = EnvironmentConfig.apiKey,
1627
1779
  let apiSecret = EnvironmentConfig.apiSecret {
1628
1780
  request.addValue(apiKey, forHTTPHeaderField: "x-api-key")
1629
1781
  request.addValue(apiSecret, forHTTPHeaderField: "x-api-secret")
1630
1782
  }
1631
-
1783
+
1632
1784
  // Prepare parameters
1633
1785
  let params: [String: Any] = [
1634
1786
  "account_uuid": account["account_uuid"] ?? "",
@@ -1646,11 +1798,11 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
1646
1798
  "account_status": account["account_status"] ?? "",
1647
1799
  "customer_id": "" // add if needed
1648
1800
  ]
1649
-
1801
+
1650
1802
  do {
1651
1803
  let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
1652
1804
  request.httpBody = jsonData
1653
-
1805
+
1654
1806
  if let jsonString = String(data: jsonData, encoding: .utf8) {
1655
1807
  print("🔍 JSON Payload:\n\(jsonString)")
1656
1808
  }
@@ -1659,30 +1811,30 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
1659
1811
  hideLoadingIndicator()
1660
1812
  return
1661
1813
  }
1662
-
1814
+
1663
1815
  let task = URLSession.shared.dataTask(with: request) { data, response, error in
1664
1816
  DispatchQueue.main.async { self.hideLoadingIndicator() }
1665
-
1817
+
1666
1818
  if let error = error {
1667
1819
  print("❌ Request Error: \(error.localizedDescription)")
1668
1820
  return
1669
1821
  }
1670
-
1822
+
1671
1823
  guard let httpResponse = response as? HTTPURLResponse else {
1672
1824
  print("❌ Invalid response")
1673
1825
  return
1674
1826
  }
1675
-
1827
+
1676
1828
  if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
1677
1829
  guard let data = data else {
1678
1830
  print("❌ No data received")
1679
1831
  return
1680
1832
  }
1681
-
1833
+
1682
1834
  do {
1683
1835
  if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
1684
1836
  print("✅ Account Connected Successfully: \(responseObject)")
1685
-
1837
+
1686
1838
  if let status = responseObject["status"] as? Int, status == 1 {
1687
1839
  self.isBankAccountConnected = true
1688
1840
  self.defaultBankAccountID = responseObject["default_account"] as? String
@@ -1690,27 +1842,156 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
1690
1842
  self.isBankAccountConnected = false
1691
1843
  self.defaultBankAccountID = nil
1692
1844
  }
1693
-
1845
+
1694
1846
  DispatchQueue.main.async {
1695
1847
  // ✅ Show saved bank UI
1696
1848
  self.viewGrailPaySavedBank.isHidden = false
1697
1849
  self.viewGrailPaySavedAccount.isHidden = false
1698
-
1850
+
1699
1851
  if let accountNumber = account["account_number"] as? String {
1700
1852
  let last4 = String(accountNumber.suffix(4))
1701
1853
  self.lblGrailPaySavedAccountNumber.text = "****\(last4)"
1702
1854
  } else {
1703
1855
  self.lblGrailPaySavedAccountNumber.text = "****----" // Default/fallback text
1704
1856
  }
1705
-
1857
+
1706
1858
  // ✅ Set account type
1707
1859
  if let accountType = account["account_type"] as? String {
1708
1860
  self.lblGrailPaySavedAccountType.text = "Type: \(accountType.capitalized)"
1709
1861
  }
1710
-
1862
+
1711
1863
  // ✅ Optionally hide abandon view if visible
1712
1864
  self.viewGrailPayAbandon.isHidden = true
1713
-
1865
+
1866
+ if self.selectedPaymentMethod == "Bank" {
1867
+ self.viewBtnShowSavedCards.isHidden = true
1868
+ self.btnShowSavedCard.isHidden = true
1869
+ self.viewBtnShowSavedCardHeight.constant = 0
1870
+ self.viewBtnShowSavedCardTopCon.constant = 0
1871
+ }
1872
+ }
1873
+ }
1874
+
1875
+ else {
1876
+ print("❌ Invalid JSON format")
1877
+ }
1878
+ } catch {
1879
+ print("❌ JSON Parsing Error: \(error)")
1880
+ }
1881
+ } else {
1882
+ print("❌ HTTP Status Code: \(httpResponse.statusCode)")
1883
+ }
1884
+ }
1885
+
1886
+ task.resume()
1887
+ }
1888
+
1889
+ //MARK: - Account Connect Api New GrailPay Account Add
1890
+ func newGrailPayAccountConnectApi(account: [String: Any]) {
1891
+ showLoadingIndicator()
1892
+
1893
+ let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.accountConnect.path()
1894
+
1895
+ guard let serviceURL = URL(string: fullURL) else {
1896
+ print("Invalid URL")
1897
+ hideLoadingIndicator()
1898
+ return
1899
+ }
1900
+
1901
+ var request = URLRequest(url: serviceURL)
1902
+ request.httpMethod = "POST"
1903
+ request.addValue("application/json", forHTTPHeaderField: "Content-Type")
1904
+
1905
+ // Add API headers
1906
+ if let apiKey = EnvironmentConfig.apiKey,
1907
+ let apiSecret = EnvironmentConfig.apiSecret {
1908
+ request.addValue(apiKey, forHTTPHeaderField: "x-api-key")
1909
+ request.addValue(apiSecret, forHTTPHeaderField: "x-api-secret")
1910
+ }
1911
+
1912
+ // Prepare parameters
1913
+ let params: [String: Any] = [
1914
+ "account_uuid": account["account_uuid"] ?? "",
1915
+ "user_uuid": account["user_uuid"] ?? "",
1916
+ "aggregator_type": account["aggregator_type"] ?? "bank_link",
1917
+ "vendor_id": account["vendor_id"] ?? "",
1918
+ "created_at": account["created_at"] ?? "",
1919
+ "updated_at": account["updated_at"] ?? "",
1920
+ "account_id": account["account_id"] ?? "",
1921
+ "account_number": account["account_number"] ?? "",
1922
+ "provider_name": account["provider_name"] ?? "",
1923
+ "routing_number": account["routing_number"] ?? "",
1924
+ "name": account["name"] ?? "",
1925
+ "account_type": account["account_type"] ?? "",
1926
+ "account_status": account["account_status"] ?? "",
1927
+ "customer_id": "" // add if needed
1928
+ ]
1929
+
1930
+ do {
1931
+ let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
1932
+ request.httpBody = jsonData
1933
+
1934
+ if let jsonString = String(data: jsonData, encoding: .utf8) {
1935
+ print("🔍 JSON Payload:\n\(jsonString)")
1936
+ }
1937
+ } catch {
1938
+ print("❌ JSON Serialization Error: \(error)")
1939
+ hideLoadingIndicator()
1940
+ return
1941
+ }
1942
+
1943
+ let task = URLSession.shared.dataTask(with: request) { data, response, error in
1944
+ DispatchQueue.main.async { self.hideLoadingIndicator() }
1945
+
1946
+ if let error = error {
1947
+ print("❌ Request Error: \(error.localizedDescription)")
1948
+ return
1949
+ }
1950
+
1951
+ guard let httpResponse = response as? HTTPURLResponse else {
1952
+ print("❌ Invalid response")
1953
+ return
1954
+ }
1955
+
1956
+ if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
1957
+ guard let data = data else {
1958
+ print("❌ No data received")
1959
+ return
1960
+ }
1961
+
1962
+ do {
1963
+ if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
1964
+ print("✅ Account Connected Successfully: \(responseObject)")
1965
+
1966
+ if let status = responseObject["status"] as? Int, status == 1 {
1967
+ DispatchQueue.main.async {
1968
+ self.isBankAccountConnected = true
1969
+ self.defaultNewBankAccountID = responseObject["default_account"] as? String
1970
+ self.isSavedNewGrailPayAccount = true
1971
+ self.updateUIForNewGrailPayAccount()
1972
+ }
1973
+ }
1974
+
1975
+ DispatchQueue.main.async {
1976
+ // ✅ Show saved bank UI
1977
+ self.grailPayNewSavedAccountView.isHidden = false
1978
+ self.viewGrailPayNewAccountDetail.isHidden = false
1979
+
1980
+ if let accountNumber = account["account_number"] as? String {
1981
+ let last4 = String(accountNumber.suffix(4))
1982
+ self.lblGrailPayNewAccountNumber.text = "****\(last4)"
1983
+ } else {
1984
+ self.lblGrailPayNewAccountNumber.text = "****----" // Default/fallback text
1985
+ }
1986
+
1987
+ // ✅ Set account type
1988
+ if let accountType = account["account_type"] as? String {
1989
+ self.lblGrailPayNewAccountType.text = "Type: \(accountType.capitalized)"
1990
+ }
1991
+
1992
+ // ✅ Optionally hide abandon view if visible
1993
+ self.viewAbandonGrailPayNewAccountView.isHidden = true
1994
+
1714
1995
  if self.selectedPaymentMethod == "Bank" {
1715
1996
  self.viewBtnShowSavedCards.isHidden = true
1716
1997
  self.btnShowSavedCard.isHidden = true
@@ -1719,7 +2000,7 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
1719
2000
  }
1720
2001
  }
1721
2002
  }
1722
-
2003
+
1723
2004
  else {
1724
2005
  print("❌ Invalid JSON format")
1725
2006
  }
@@ -1730,10 +2011,11 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
1730
2011
  print("❌ HTTP Status Code: \(httpResponse.statusCode)")
1731
2012
  }
1732
2013
  }
1733
-
2014
+
1734
2015
  task.resume()
1735
2016
  }
1736
-
2017
+
2018
+
1737
2019
  // MARK: - Action Button Next
1738
2020
  @IBAction func actionBtnNext(_ sender: UIButton) {
1739
2021
  // Check if billing info data exists and is valid JSON
@@ -1744,27 +2026,27 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
1744
2026
  let jsonDict = json as? [String: Any], !jsonDict.isEmpty else {
1745
2027
  return
1746
2028
  }
1747
-
2029
+
1748
2030
  DispatchQueue.main.async {
1749
2031
  // Ensure UI updates happen on the main thread
1750
2032
  guard let self = self else { return }
1751
-
2033
+
1752
2034
  if self.selectedPaymentMethod == "Card" {
1753
2035
  // Trim whitespace and newlines
1754
2036
  let cardNumber = self.cardNumberTextField.text.trimmingCharacters(in: .whitespacesAndNewlines)
1755
2037
  let expiryDate = self.cardExpiryTextField.text.trimmingCharacters(in: .whitespacesAndNewlines)
1756
2038
  let cvv = self.cardCvvTextField.text.trimmingCharacters(in: .whitespacesAndNewlines)
1757
2039
  let cardName = self.cardNameTextField.text.trimmingCharacters(in: .whitespacesAndNewlines)
1758
-
2040
+
1759
2041
  // Remove spaces from card number for validation
1760
2042
  let sanitizedCardNumber = cardNumber.replacingOccurrences(of: " ", with: "")
1761
-
2043
+
1762
2044
  // Regular expression for expiry date format MM/yyyy
1763
2045
  let expiryDateRegex = #"^(0[1-9]|1[0-2])\/\d{4}$"#
1764
-
2046
+
1765
2047
  // Regular expression for a valid name (only letters and spaces)
1766
2048
  let nameRegex = #"^[A-Za-z\s]+$"#
1767
-
2049
+
1768
2050
  // Validation checks
1769
2051
  if cardNumber.isEmpty {
1770
2052
  self.showAlert(title: "Missing Information", message: "Card Number is required.")
@@ -1801,11 +2083,21 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
1801
2083
  self.showAlert(title: "Missing Information", message: "Bank account name is required.")
1802
2084
  } else if self.txtFieldRoutingNumber.text?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == true {
1803
2085
  self.showAlert(title: "Missing Information", message: "Routing number is required.")
1804
- } else if self.txtFieldAccountType.text?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == true {
2086
+ }
2087
+ else if let routingNumber = self.txtFieldRoutingNumber.text?.replacingOccurrences(of: " ", with: ""), routingNumber.count != 9 || !CharacterSet.decimalDigits.isSuperset(of: CharacterSet(charactersIn: routingNumber)) {
2088
+ self.showAlert(title: "Invalid Routing Number", message: "Routing number is not correct. It must be exactly 9 digits.")
2089
+ }
2090
+ else if self.txtFieldAccountType.text?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == true {
1805
2091
  self.showAlert(title: "Missing Information", message: "Bank account type is required.")
1806
2092
  } else if self.txtFieldAccountNumber.text?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == true {
1807
2093
  self.showAlert(title: "Missing Information", message: "Bank account number is required.")
1808
2094
  }
2095
+ else if self.txtFieldConfirmBankAccount.text?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == true {
2096
+ self.showAlert(title: "Missing Information", message: "Please confirm your bank account number.")
2097
+ }
2098
+ else if self.txtFieldAccountNumber.text?.trimmingCharacters(in: .whitespacesAndNewlines) != self.txtFieldConfirmBankAccount.text?.trimmingCharacters(in: .whitespacesAndNewlines) {
2099
+ self.showAlert(title: "Account Mismatch", message: "Bank account number and confirmation do not match.")
2100
+ }
1809
2101
  else if !self.agreeTermsAndCondtition {
1810
2102
  self.showAlert(title: "Terms and Conditions", message: "Please agree to the terms and conditions")
1811
2103
  return
@@ -1830,22 +2122,22 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
1830
2122
  // Billing info is nil or empty
1831
2123
  if selectedPaymentMethod == "Card" {
1832
2124
  /// Card Case
1833
-
2125
+
1834
2126
  // Trim whitespace and newlines
1835
2127
  let cardNumber = self.cardNumberTextField.text.trimmingCharacters(in: .whitespacesAndNewlines)
1836
2128
  let expiryDate = self.cardExpiryTextField.text.trimmingCharacters(in: .whitespacesAndNewlines)
1837
2129
  let cvv = self.cardCvvTextField.text.trimmingCharacters(in: .whitespacesAndNewlines)
1838
2130
  let cardName = self.cardNameTextField.text.trimmingCharacters(in: .whitespacesAndNewlines)
1839
-
2131
+
1840
2132
  // Remove spaces from card number for validation
1841
2133
  let sanitizedCardNumber = cardNumber.replacingOccurrences(of: " ", with: "")
1842
-
2134
+
1843
2135
  // Regular expression for expiry date format MM/yyyy
1844
2136
  let expiryDateRegex = #"^(0[1-9]|1[0-2])\/\d{4}$"#
1845
-
2137
+
1846
2138
  // Regular expression for a valid name (only letters and spaces)
1847
2139
  let nameRegex = #"^[A-Za-z\s]+$"#
1848
-
2140
+
1849
2141
  // Validation checks
1850
2142
  if cardNumber.isEmpty {
1851
2143
  self.showAlert(title: "Missing Information", message: "Card Number is required.")
@@ -1877,13 +2169,25 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
1877
2169
  // Bank Case
1878
2170
  if txtFieldAccountName.text?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == true {
1879
2171
  showAlert(title: "Missing Information", message: "Bank account name is required.")
1880
- } else if txtFieldRoutingNumber.text?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == true {
2172
+ }
2173
+ else if txtFieldRoutingNumber.text?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == true {
1881
2174
  showAlert(title: "Missing Information", message: "Routing number is required.")
1882
- } else if txtFieldAccountType.text?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == true {
2175
+ }
2176
+ else if let routingNumber = self.txtFieldRoutingNumber.text?.replacingOccurrences(of: " ", with: ""), routingNumber.count != 9 || !CharacterSet.decimalDigits.isSuperset(of: CharacterSet(charactersIn: routingNumber)) {
2177
+ self.showAlert(title: "Invalid Routing Number", message: "Routing number is not correct. It must be exactly 9 digits.")
2178
+ }
2179
+ else if txtFieldAccountType.text?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == true {
1883
2180
  showAlert(title: "Missing Information", message: "Bank account type is required.")
1884
- } else if txtFieldAccountNumber.text?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == true {
2181
+ }
2182
+ else if txtFieldAccountNumber.text?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == true {
1885
2183
  showAlert(title: "Missing Information", message: "Bank account number is required.")
1886
2184
  }
2185
+ else if self.txtFieldConfirmBankAccount.text?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == true {
2186
+ self.showAlert(title: "Missing Information", message: "Please confirm your bank account number.")
2187
+ }
2188
+ else if self.txtFieldAccountNumber.text?.trimmingCharacters(in: .whitespacesAndNewlines) != self.txtFieldConfirmBankAccount.text?.trimmingCharacters(in: .whitespacesAndNewlines) {
2189
+ self.showAlert(title: "Account Mismatch", message: "Bank account number and confirmation do not match.")
2190
+ }
1887
2191
  else if !agreeTermsAndCondtition {
1888
2192
  showAlert(title: "Terms and Conditions", message: "Please agree to the terms and conditions")
1889
2193
  return
@@ -1898,7 +2202,7 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
1898
2202
  }
1899
2203
  }
1900
2204
  }
1901
-
2205
+
1902
2206
  // Function to navigate to EmailVerificationVC
1903
2207
  func navigateCardDataToEmailVerificationVC() {
1904
2208
  if let emailVerificationVC = storyboard?.instantiateViewController(withIdentifier: "EmailVerificationVC") as? EmailVerificationVC {
@@ -1912,7 +2216,7 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
1912
2216
  self.navigationController?.pushViewController(emailVerificationVC, animated: true)
1913
2217
  }
1914
2218
  }
1915
-
2219
+
1916
2220
  func navigateBankDataToEmailVerificationVC() {
1917
2221
  if let emailVerificationVC = storyboard?.instantiateViewController(withIdentifier: "EmailVerificationVC") as? EmailVerificationVC {
1918
2222
  emailVerificationVC.accountName = txtFieldAccountName.text
@@ -1925,11 +2229,11 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
1925
2229
  self.navigationController?.pushViewController(emailVerificationVC, animated: true)
1926
2230
  }
1927
2231
  }
1928
-
2232
+
1929
2233
  @IBAction func actionBtnSettings(_ sender: UIButton) {
1930
2234
  settingsView.isHidden = false
1931
2235
  }
1932
-
2236
+
1933
2237
  @IBAction func actionBtnLogout(_ sender: UIButton) {
1934
2238
  UserStoreSingleton.shared.clearUserData()
1935
2239
  UserStoreSingleton.shared.isLoggedIn = false
@@ -1942,7 +2246,7 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
1942
2246
  txtFieldOTPText4.text = ""
1943
2247
  txtFieldOTPText5.text = ""
1944
2248
  txtFieldOTPText6.text = ""
1945
-
2249
+
1946
2250
  if selectedPaymentMethod == "Card" {
1947
2251
  self.viewSingleSavedAccount.isHidden = true
1948
2252
  self.viewBankFields.isHidden = true
@@ -1985,32 +2289,32 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
1985
2289
  self.viewChangedAccount.isHidden = true
1986
2290
  }
1987
2291
  }
1988
-
2292
+
1989
2293
  @IBAction func actionBtnCloseSettingView(_ sender: UIButton) {
1990
2294
  settingsView.isHidden = true
1991
2295
  }
1992
-
2296
+
1993
2297
  //MARK: - Bank View Buttons Actions
1994
2298
  @IBAction func actionBtnSelectAccountType(_ sender: UIButton) {
1995
2299
  UIView.animate(withDuration: 0.3) {
1996
2300
  self.viewAccountType.isHidden.toggle()
1997
2301
  }
1998
2302
  }
1999
-
2303
+
2000
2304
  @IBAction func actionBtnSaveAccountForFuture(_ sender: UIButton) {
2001
2305
  isSavedForFuture.toggle()
2002
2306
  // Change the image based on the state
2003
2307
  let imageName = isSavedForFuture ? "checkmark.square.fill" : "square"
2004
2308
  btnCheckSavedAccountForFuture.setImage(UIImage(systemName: imageName), for: .normal)
2005
2309
  }
2006
-
2310
+
2007
2311
  @IBAction func actionBtnCheckAgreeTermsConditions(_ sender: UIButton) {
2008
2312
  agreeTermsAndCondtition.toggle()
2009
2313
  // Change the image based on the state
2010
2314
  let imageName = agreeTermsAndCondtition ? "checkmark.square.fill" : "square"
2011
2315
  btnAgreeTermsConditions.setImage(UIImage(systemName: imageName), for: .normal)
2012
2316
  }
2013
-
2317
+
2014
2318
  @IBAction func actionBtnSelectSingleAccountView(_ sender: UIButton) {
2015
2319
  // Toggle the boolean property
2016
2320
  isSelectForPayBank.toggle()
@@ -2021,42 +2325,42 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
2021
2325
  if isSelectForPayBank {
2022
2326
  viewTermAndConditionsSingleAccountView.isHidden = false
2023
2327
  btnPayNowSingleAccountView.isHidden = false
2024
- viewSingleAccountViewHeight.constant = 170
2328
+ // viewSingleAccountViewHeight.constant = 170
2025
2329
  } else {
2026
2330
  viewTermAndConditionsSingleAccountView.isHidden = true
2027
2331
  btnPayNowSingleAccountView.isHidden = true
2028
- viewSingleAccountViewHeight.constant = 68
2332
+ // viewSingleAccountViewHeight.constant = 68
2029
2333
  btnNext.isHidden = true
2030
2334
  btnNextHeight.constant = 0
2031
2335
  btnNextTopCon.constant = 0
2032
2336
  }
2033
2337
  }
2034
-
2338
+
2035
2339
  @IBAction func actionBtnChangeSingleAccountView(_ sender: UIButton) {
2036
2340
  self.viewChangedAccount.isHidden = false
2037
2341
  self.viewBankFields.isHidden = true
2038
2342
  self.viewSingleSavedAccount.isHidden = true
2039
2343
  }
2040
-
2344
+
2041
2345
  @IBAction func actionBtnAgreeTermsConditionsSingleAccountView(_ sender: UIButton) {
2042
2346
  agreeTermsAndCondtition.toggle()
2043
2347
  // Change the image based on the state
2044
2348
  let imageName = agreeTermsAndCondtition ? "checkmark.square.fill" : "square"
2045
2349
  btnCheckAgreeTermsAndConditionsSingleAccount.setImage(UIImage(systemName: imageName), for: .normal)
2046
2350
  }
2047
-
2351
+
2048
2352
  @IBAction func actionBtnPayNowSingleAccountView(_ sender: UIButton) {
2049
2353
  // Check if billingInfoData is not nil or empty
2050
2354
  if let billingInfoData = request.billingInfoData,
2051
2355
  let json = try? JSONSerialization.jsonObject(with: billingInfoData, options: []),
2052
2356
  let jsonDict = json as? [String: Any], !jsonDict.isEmpty {
2053
-
2357
+
2054
2358
  // Check if the terms and conditions are agreed
2055
2359
  if !agreeTermsAndCondtition {
2056
2360
  showAlert(title: "Terms and Conditions", message: "Please agree to the terms and conditions")
2057
2361
  return
2058
2362
  }
2059
-
2363
+
2060
2364
  // Proceed with the Pay Now action
2061
2365
  let vc = easymerchantsdk.instantiateViewController(withIdentifier: "BillingInfoVC") as! BillingInfoVC
2062
2366
  // Pass the customer_id and account_id to BillingInfoVC
@@ -2073,54 +2377,67 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
2073
2377
  showAlert(title: "Terms and Conditions", message: "Please agree to the terms and conditions")
2074
2378
  return
2075
2379
  }
2076
-
2380
+
2077
2381
  accountChargeSavedBankAccountApi()
2078
2382
  }
2079
2383
  }
2080
-
2384
+
2081
2385
  @IBAction func actionBtnCloseChangeAccountView(_ sender: UIButton) {
2082
2386
  viewChangedAccount.isHidden = true
2083
2387
  viewSingleSavedAccount.isHidden = false
2084
2388
  }
2085
-
2389
+
2086
2390
  @IBAction func actionBtnSaveAccountNewAccountView(_ sender: UIButton) {
2087
2391
  isSavedNewAccount.toggle()
2088
2392
  // Change the image based on the state
2089
2393
  let imageName = isSavedNewAccount ? "checkmark.square.fill" : "square"
2090
2394
  btnSavedNewAccountForFuture.setImage(UIImage(systemName: imageName), for: .normal)
2091
2395
  }
2092
-
2396
+
2093
2397
  @IBAction func actionBtnAccontTypeNewAccountView(_ sender: UIButton) {
2094
2398
  UIView.animate(withDuration: 0.3) {
2095
2399
  self.viewAccountTypeNewAccountView.isHidden.toggle()
2096
2400
  }
2097
2401
  }
2098
-
2402
+
2099
2403
  @IBAction func actionBtnPayNowNewAccountView(_ sender: UIButton) {
2100
2404
  // Check if billingInfoData is not nil or empty
2101
2405
  if let billingInfoData = request.billingInfoData,
2102
2406
  let json = try? JSONSerialization.jsonObject(with: billingInfoData, options: []),
2103
2407
  let jsonDict = json as? [String: Any], !jsonDict.isEmpty {
2104
2408
  print("Billing Info Data: \(jsonDict)")
2105
-
2409
+
2106
2410
  // Retrieve and trim text field values (removing leading and trailing whitespace)
2107
2411
  let accountName = txtFieldAccountNameNewAccountView.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
2108
2412
  let routingNumber = txtFieldRoutingNumberNewAccountView.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
2109
2413
  let accountType = txtFieldAccountTypeNewAccountView.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
2110
2414
  let accountNumber = txtFieldAccountNumberNewAccountView.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
2111
-
2415
+ let confirmAccountNumber = txtFieldConfirmAccountNewAccountView.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
2416
+
2112
2417
  // Check if any field is empty
2113
- if accountName.isEmpty || routingNumber.isEmpty || accountType.isEmpty || accountNumber.isEmpty {
2418
+ if accountName.isEmpty || routingNumber.isEmpty || accountType.isEmpty || accountNumber.isEmpty || confirmAccountNumber.isEmpty {
2114
2419
  let alert = UIAlertController(title: "Missing Information", message: "Please fill in all bank details.", preferredStyle: .alert)
2115
2420
  alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
2116
2421
  self.present(alert, animated: true, completion: nil)
2117
2422
  return
2118
2423
  }
2119
-
2424
+
2425
+ // Check if routing number is exactly 9 digits and numeric
2426
+ if routingNumber.count != 9 || !CharacterSet.decimalDigits.isSuperset(of: CharacterSet(charactersIn: routingNumber)) {
2427
+ self.showAlert(title: "Invalid Routing Number", message: "Routing number is not correct. It must be exactly 9 digits.")
2428
+ return
2429
+ }
2430
+
2431
+ // Check if account number and confirmation match
2432
+ if accountNumber != confirmAccountNumber {
2433
+ self.showAlert(title: "Account Mismatch", message: "Bank account number and confirmation do not match.")
2434
+ return
2435
+ }
2436
+
2120
2437
  // Determine the vc.isFrom value based on isSavedNewAccount
2121
2438
  let isSavedNewAccount = isSavedNewAccount
2122
2439
  let isFromValue = isSavedNewAccount ? "AddNewAccountWithSave" : "AddNewAccountWithoutSave"
2123
-
2440
+
2124
2441
  // Proceed with the Pay Now action
2125
2442
  let vc = easymerchantsdk.instantiateViewController(withIdentifier: "BillingInfoVC") as! BillingInfoVC
2126
2443
  vc.accountName = accountName
@@ -2147,7 +2464,7 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
2147
2464
  }
2148
2465
  }
2149
2466
  }
2150
-
2467
+
2151
2468
  @IBAction func actionBtnCloseNewAccountView(_ sender: UIButton) {
2152
2469
  self.viewNewBankAccount.isHidden = true
2153
2470
  self.viewSingleSavedAccount.isHidden = true
@@ -2167,7 +2484,7 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
2167
2484
  self.btnNextTopCon.constant = 8
2168
2485
  self.viewChangedAccount.isHidden = false
2169
2486
  }
2170
-
2487
+
2171
2488
  //MARK: - Crypto View Buttons Actions
2172
2489
  @IBAction func actionBtnTryAgain(_ sender: UIButton) {
2173
2490
  // Call the crypto charge API again
@@ -2182,18 +2499,18 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
2182
2499
  self.startCryptoTimer()
2183
2500
  }
2184
2501
  }
2185
-
2502
+
2186
2503
  func setOnChainSelected() {
2187
2504
  // Retrieve primary and secondary button colors
2188
2505
  if let primaryBtnBackGroundColor = UserStoreSingleton.shared.primary_btn_bg_col,
2189
2506
  let primaryUIColor = UIColor(hex: primaryBtnBackGroundColor),
2190
2507
  let secondaryBtnBackgroundColor = UserStoreSingleton.shared.primary_btn_font_col,
2191
2508
  let secondaryUIColor = UIColor(hex: secondaryBtnBackgroundColor) {
2192
-
2509
+
2193
2510
  // Set the selected state for OnChain button
2194
2511
  btnOnChain.backgroundColor = primaryUIColor
2195
2512
  btnOnChain.setTitleColor(secondaryUIColor, for: .normal)
2196
-
2513
+
2197
2514
  // Reset the state for Lightning button
2198
2515
  btnLightning.backgroundColor = .clear
2199
2516
  btnLightning.setTitleColor(primaryUIColor, for: .normal)
@@ -2207,7 +2524,7 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
2207
2524
  btnLightning.setTitleColor(.systemBlue, for: .normal)
2208
2525
  }
2209
2526
  }
2210
-
2527
+
2211
2528
  @IBAction func actionBtnOnChain(_ sender: UIButton) {
2212
2529
  // Set the selected state for OnChain button
2213
2530
  setOnChainSelected()
@@ -2217,18 +2534,18 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
2217
2534
  lblBTCAddress.text = "BTC Address: \(address)" // Update lblBTCAddress with the Chain Invoice Address
2218
2535
  }
2219
2536
  }
2220
-
2537
+
2221
2538
  @IBAction func actionBtnLightning(_ sender: UIButton) {
2222
2539
  // Retrieve primary and secondary button colors
2223
2540
  if let primaryBtnBackGroundColor = UserStoreSingleton.shared.primary_btn_bg_col,
2224
2541
  let primaryUIColor = UIColor(hex: primaryBtnBackGroundColor),
2225
2542
  let secondaryBtnBackgroundColor = UserStoreSingleton.shared.primary_btn_font_col,
2226
2543
  let secondaryUIColor = UIColor(hex: secondaryBtnBackgroundColor) {
2227
-
2544
+
2228
2545
  // Set the selected state for OnChain button
2229
2546
  btnLightning.backgroundColor = primaryUIColor
2230
2547
  btnLightning.setTitleColor(secondaryUIColor, for: .normal)
2231
-
2548
+
2232
2549
  // Reset the state for Lightning button
2233
2550
  btnOnChain.backgroundColor = .clear
2234
2551
  btnOnChain.setTitleColor(primaryUIColor, for: .normal)
@@ -2241,37 +2558,37 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
2241
2558
  btnOnChain.backgroundColor = .clear
2242
2559
  btnOnChain.setTitleColor(.systemBlue, for: .normal)
2243
2560
  }
2244
-
2561
+
2245
2562
  // Generate QR code for Lightning URI
2246
2563
  if let uri = lightningURI {
2247
2564
  qrImageView.image = generateQRCode(from: uri)
2248
2565
  lblBTCAddress.text = "BTC Address: \(uri)" // Update lblBTCAddress with the Lightning URI
2249
2566
  }
2250
2567
  }
2251
-
2568
+
2252
2569
  @IBAction func actionBtnCopyBTCAddress(_ sender: UIButton) {
2253
2570
  // Check if there is text to copy
2254
2571
  guard let btcAddressText = lblBTCAddress.text, !btcAddressText.isEmpty else {
2255
2572
  print("No address to copy")
2256
2573
  return
2257
2574
  }
2258
-
2575
+
2259
2576
  // Extract the actual BTC address from the label text
2260
2577
  let components = btcAddressText.components(separatedBy: "BTC Address: ")
2261
2578
  if components.count > 1 {
2262
2579
  let btcAddress = components[1]
2263
-
2580
+
2264
2581
  // Copy the address to the clipboard
2265
2582
  UIPasteboard.general.string = btcAddress
2266
-
2583
+
2267
2584
  // Print success message to the console
2268
2585
  print("Address copied to clipboard successfully: \(btcAddress)")
2269
-
2586
+
2270
2587
  // Optionally, show a toast message to the user
2271
2588
  showToast(message: "BTC Address copied to clipboard")
2272
2589
  }
2273
2590
  }
2274
-
2591
+
2275
2592
  //MARK: - Card Scan Button Action
2276
2593
  @IBAction func actionButtonScanCard(_ sender: UIButton) {
2277
2594
  // // Validate BlinkCard License
@@ -2281,66 +2598,66 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
2281
2598
  // showLicenseErrorAlert(message: licenseErrorMessage)
2282
2599
  // return
2283
2600
  // }
2284
- //
2601
+ //
2285
2602
  // // Proceed with card scanning if the license is valid
2286
2603
  // blinkCardRecognizer = MBCBlinkCardRecognizer()
2287
2604
  // blinkCardRecognizer.returnFullDocumentImage = true
2288
- //
2605
+ //
2289
2606
  // let recognizerList = [blinkCardRecognizer!]
2290
2607
  // let recognizerCollection = MBCRecognizerCollection(recognizers: recognizerList)
2291
- //
2608
+ //
2292
2609
  // let customOverlayViewController: CustomOverlay = CustomOverlay.initFromStoryboard()
2293
- //
2610
+ //
2294
2611
  // // Reconfigure recognizers with the overlay
2295
2612
  // customOverlayViewController.reconfigureRecognizers(recognizerCollection)
2296
- //
2613
+ //
2297
2614
  // // Create the recognizer runner view controller with custom overlay
2298
2615
  // guard let recognizerRunnerViewController =
2299
2616
  // MBCViewControllerFactory.recognizerRunnerViewController(withOverlayViewController: customOverlayViewController) else {
2300
2617
  // showLicenseErrorAlert(message: "Failed to initialize the recognizer view controller.")
2301
2618
  // return
2302
2619
  // }
2303
- //
2620
+ //
2304
2621
  // customOverlayViewController.delegate = self
2305
2622
  // recognizerRunnerViewController.modalPresentationStyle = .fullScreen
2306
- //
2623
+ //
2307
2624
  // // Present the recognizer runner view controller
2308
2625
  // self.present(recognizerRunnerViewController, animated: true, completion: nil)
2309
2626
  }
2310
-
2627
+
2311
2628
  private func showLicenseErrorAlert(message: String) {
2312
2629
  let alert = UIAlertController(title: "License Error", message: message, preferredStyle: .alert)
2313
2630
  alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
2314
2631
  self.present(alert, animated: true, completion: nil)
2315
2632
  }
2316
-
2633
+
2317
2634
  //MARK: - Send OTP Email Verification Api
2318
2635
  func emailVerificationApi() {
2319
2636
  showLoadingIndicator()
2320
- // Construct the full URL using baseURL from EnvironmentConfig and path from the endpoint
2637
+
2321
2638
  let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.emailVerification.path()
2322
-
2639
+
2323
2640
  guard let serviceURL = URL(string: fullURL) else {
2324
2641
  print("Invalid URL")
2325
2642
  hideLoadingIndicator()
2326
2643
  return
2327
2644
  }
2328
-
2645
+
2329
2646
  var request = URLRequest(url: serviceURL)
2330
2647
  request.httpMethod = "POST"
2331
2648
  request.addValue("application/json", forHTTPHeaderField: "Content-Type")
2332
-
2649
+
2333
2650
  // Use the clientToken from your singleton
2334
2651
  let token = UserStoreSingleton.shared.clientToken
2335
2652
  print("Setting clientToken header: \(token ?? "None")")
2336
2653
  request.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
2337
-
2654
+
2338
2655
  // Define the parameters for the request
2339
2656
  let params: [String: Any] = [
2340
2657
  "card_search_value": txtFieldEmail.text ?? "",
2341
2658
  "card_search_key": "email"
2342
2659
  ]
2343
-
2660
+
2344
2661
  do {
2345
2662
  // Convert the dictionary to JSON
2346
2663
  let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
@@ -2353,24 +2670,24 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
2353
2670
  hideLoadingIndicator()
2354
2671
  return
2355
2672
  }
2356
-
2673
+
2357
2674
  let session = URLSession.shared
2358
2675
  let task = session.dataTask(with: request) { (serviceData, serviceResponse, error) in
2359
-
2676
+
2360
2677
  DispatchQueue.main.async {
2361
2678
  self.hideLoadingIndicator() // Stop loader when response is received
2362
2679
  }
2363
-
2680
+
2364
2681
  if let error = error {
2365
2682
  print("Error: \(error.localizedDescription)")
2366
2683
  return
2367
2684
  }
2368
-
2685
+
2369
2686
  guard let httpResponse = serviceResponse as? HTTPURLResponse else {
2370
2687
  print("Invalid response")
2371
2688
  return
2372
2689
  }
2373
-
2690
+
2374
2691
  if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
2375
2692
  if let data = serviceData {
2376
2693
  do {
@@ -2394,37 +2711,37 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
2394
2711
  }
2395
2712
  task.resume()
2396
2713
  }
2397
-
2714
+
2398
2715
  //MARK: - OTP Verification Api
2399
2716
  func otpVerificationApi() {
2400
2717
  showLoadingIndicator()
2401
-
2718
+
2402
2719
  let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.verifyOtp.path()
2403
-
2720
+
2404
2721
  guard let serviceURL = URL(string: fullURL) else {
2405
2722
  print("Invalid URL")
2406
2723
  hideLoadingIndicator()
2407
2724
  return
2408
2725
  }
2409
-
2726
+
2410
2727
  var request = URLRequest(url: serviceURL)
2411
2728
  request.httpMethod = "POST"
2412
2729
  request.addValue("application/json", forHTTPHeaderField: "Content-Type")
2413
-
2730
+
2414
2731
  let token = UserStoreSingleton.shared.clientToken
2415
2732
  print("Setting clientToken header: \(token ?? "None")")
2416
2733
  request.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
2417
-
2734
+
2418
2735
  // ✅ Safe access from main thread (assumes you call this from main thread)
2419
2736
  let email = self.txtFieldEmail.text ?? ""
2420
2737
  let otp = getCombinedOTP()
2421
-
2738
+
2422
2739
  let params: [String: Any] = [
2423
2740
  "card_search_value": email,
2424
2741
  "card_search_key": "email",
2425
2742
  "otp": otp
2426
2743
  ]
2427
-
2744
+
2428
2745
  do {
2429
2746
  let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
2430
2747
  request.httpBody = jsonData
@@ -2436,54 +2753,54 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
2436
2753
  hideLoadingIndicator()
2437
2754
  return
2438
2755
  }
2439
-
2756
+
2440
2757
  let task = URLSession.shared.dataTask(with: request) { serviceData, serviceResponse, error in
2441
-
2758
+
2442
2759
  DispatchQueue.main.async {
2443
2760
  self.hideLoadingIndicator()
2444
2761
  }
2445
-
2762
+
2446
2763
  if let error = error {
2447
2764
  print("Error: \(error.localizedDescription)")
2448
2765
  return
2449
2766
  }
2450
-
2767
+
2451
2768
  guard let httpResponse = serviceResponse as? HTTPURLResponse else {
2452
2769
  print("Invalid response")
2453
2770
  return
2454
2771
  }
2455
-
2772
+
2456
2773
  if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
2457
2774
  guard let data = serviceData else {
2458
2775
  print("No data received")
2459
2776
  return
2460
2777
  }
2461
-
2778
+
2462
2779
  do {
2463
2780
  if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
2464
2781
  print("Response Data: \(responseObject)")
2465
-
2782
+
2466
2783
  guard let responseData = responseObject["data"] as? [String: Any],
2467
2784
  let nestedData = responseData["data"] as? [String: Any] else {
2468
2785
  print("Invalid response structure or missing keys")
2469
2786
  return
2470
2787
  }
2471
-
2788
+
2472
2789
  let customerId = nestedData["customer_id"] as? String ?? ""
2473
2790
  let customerToken = nestedData["customer_token"] as? String ?? ""
2474
-
2791
+
2475
2792
  DispatchQueue.main.async {
2476
2793
  UserStoreSingleton.shared.customerId = customerId
2477
2794
  UserStoreSingleton.shared.customerToken = customerToken
2478
2795
  UserStoreSingleton.shared.verificationEmail = email
2479
2796
  UserStoreSingleton.shared.isLoggedIn = true
2480
-
2797
+
2481
2798
  print("Saved Customer ID: \(UserStoreSingleton.shared.customerId ?? "nil")")
2482
2799
  print("Saved Customer Token: \(UserStoreSingleton.shared.customerToken ?? "nil")")
2483
2800
  print("Saved Verification Email: \(UserStoreSingleton.shared.verificationEmail ?? "nil")")
2484
-
2801
+
2485
2802
  self.btnSettings.isHidden = false
2486
-
2803
+
2487
2804
  if self.selectedPaymentMethod == "Card" {
2488
2805
  if customerId.isEmpty {
2489
2806
  self.OTPView.isHidden = true
@@ -2506,7 +2823,7 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
2506
2823
  self.viewChangeCard.isHidden = true
2507
2824
  self.viewUpdateCard.isHidden = true
2508
2825
  self.viewAddNewCard.isHidden = true
2509
-
2826
+
2510
2827
  self.viewTxtFieldCVVSingleCard.isHidden = true
2511
2828
  self.btnPayNowSingleCard.isHidden = true
2512
2829
  self.viewSingleSavedCardHeight.constant = 60
@@ -2518,73 +2835,61 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
2518
2835
  self.getShowBankAccountsApi()
2519
2836
  }
2520
2837
  }
2521
-
2838
+
2522
2839
  } else {
2523
2840
  print("Invalid JSON format")
2524
2841
  }
2525
2842
  } catch {
2526
2843
  print("Error parsing JSON: \(error)")
2527
2844
  }
2528
-
2845
+
2529
2846
  } else {
2530
2847
  print("HTTP Status Code: \(httpResponse.statusCode)")
2531
2848
  }
2532
2849
  }
2533
-
2850
+
2534
2851
  task.resume()
2535
2852
  }
2536
-
2853
+
2537
2854
  //MARK: - GET Show Cards API
2538
2855
  func getShowCardsApi() {
2539
2856
  showLoadingIndicator()
2540
-
2541
- // var components = URLComponents()
2542
- // components.scheme = "https"
2543
- // components.host = "stage-api.stage-easymerchant.io"
2544
- // components.path = "/api/v1/card"
2545
- //
2546
- // guard let serviceURL = components.url else {
2547
- // print("Invalid URL")
2548
- // hideLoadingIndicator()
2549
- // return
2550
- // }
2551
-
2552
- // Construct the full URL using baseURL from EnvironmentConfig and path from the endpoint
2857
+
2553
2858
  let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.getCards.path()
2554
-
2859
+
2555
2860
  guard let serviceURL = URL(string: fullURL) else {
2556
2861
  print("Invalid URL")
2557
2862
  hideLoadingIndicator()
2558
2863
  return
2559
2864
  }
2560
-
2865
+
2561
2866
  var request = URLRequest(url: serviceURL)
2562
2867
  request.httpMethod = "GET"
2563
2868
  request.addValue("application/json", forHTTPHeaderField: "Content-Type")
2564
-
2869
+
2565
2870
  let token = UserStoreSingleton.shared.customerToken
2566
2871
  print("Setting customerToken header: \(token ?? "None")")
2567
2872
  request.addValue(token ?? "", forHTTPHeaderField: "Customer-Token")
2568
-
2873
+
2569
2874
  let session = URLSession.shared
2570
2875
  let task = session.dataTask(with: request) { [weak self] (data, response, error) in
2571
2876
  guard let self = self else { return }
2572
-
2877
+
2573
2878
  DispatchQueue.main.async {
2574
2879
  self.hideLoadingIndicator() // Stop loader when response is received
2575
2880
  }
2576
-
2881
+
2577
2882
  if let error = error {
2578
2883
  print("Error: \(error.localizedDescription)")
2579
2884
  return
2580
2885
  }
2581
-
2886
+
2582
2887
  guard let data = data else { return }
2583
-
2888
+
2584
2889
  do {
2585
2890
  if let jsonResponse = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
2586
2891
  let cards = jsonResponse["Cards"] as? [[String: Any]] {
2587
-
2892
+
2588
2893
  // Map JSON response to CardModel array
2589
2894
  self.savedCards = cards.compactMap { cardDict in
2590
2895
  return CardModel(
@@ -2597,13 +2902,13 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
2597
2902
  isDefault: (cardDict["is_default"] as? Int ?? 0) == 1
2598
2903
  )
2599
2904
  }
2600
-
2905
+
2601
2906
  DispatchQueue.main.async {
2602
2907
  if let firstCard = self.savedCards.first {
2603
2908
  self.lblCardNumberSigleSavedCard.text = "****\(firstCard.ccLast4)"
2604
2909
  self.lblExpireDateSingelSavedCard.text = "Expiry: \(firstCard.ccValidThru)"
2605
2910
  }
2606
-
2911
+
2607
2912
  self.tblViewSavedCardsList.reloadData()
2608
2913
  }
2609
2914
  }
@@ -2613,39 +2918,27 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
2613
2918
  }
2614
2919
  task.resume()
2615
2920
  }
2616
-
2921
+
2617
2922
  // MARK: - Credit Card Charge Api If Billing info is nil and Without Login.
2618
2923
  func paymentIntentApi() {
2619
2924
  showLoadingIndicator()
2620
-
2621
- // var components = URLComponents()
2622
- // components.scheme = "https"
2623
- // components.host = "stage-api.stage-easymerchant.io"
2624
- // components.path = "/api/v1/charges"
2625
- //
2626
- // guard let serviceURL = components.url else {
2627
- // print("Invalid URL")
2628
- // hideLoadingIndicator()
2629
- // return
2630
- // }
2631
-
2632
- // Construct the full URL using baseURL from EnvironmentConfig and path from the endpoint
2925
+
2633
2926
  let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.creditCharges.path()
2634
-
2927
+
2635
2928
  guard let serviceURL = URL(string: fullURL) else {
2636
2929
  print("Invalid URL")
2637
2930
  hideLoadingIndicator()
2638
2931
  return
2639
2932
  }
2640
-
2933
+
2641
2934
  var request = URLRequest(url: serviceURL)
2642
2935
  request.httpMethod = "POST"
2643
2936
  request.addValue("application/json", forHTTPHeaderField: "Content-Type")
2644
-
2937
+
2645
2938
  let token = UserStoreSingleton.shared.clientToken
2646
2939
  print("Setting clientToken header: \(token ?? "None")")
2647
2940
  request.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
2648
-
2941
+
2649
2942
  let params: [String: Any] = [
2650
2943
  "name": cardNameTextField.text,
2651
2944
  "email": "test@gmail.com",
@@ -2658,7 +2951,7 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
2658
2951
  "currency": "usd",
2659
2952
  "payment_method": "card"
2660
2953
  ]
2661
-
2954
+
2662
2955
  do {
2663
2956
  let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
2664
2957
  request.httpBody = jsonData
@@ -2670,30 +2963,30 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
2670
2963
  hideLoadingIndicator()
2671
2964
  return
2672
2965
  }
2673
-
2966
+
2674
2967
  let session = URLSession.shared
2675
2968
  let task = session.dataTask(with: request) { (serviceData, serviceResponse, error) in
2676
-
2969
+
2677
2970
  DispatchQueue.main.async {
2678
2971
  self.hideLoadingIndicator() // Stop loader when response is received
2679
2972
  }
2680
-
2973
+
2681
2974
  if let error = error {
2682
2975
  print("Error: \(error.localizedDescription)")
2683
2976
  return
2684
2977
  }
2685
-
2978
+
2686
2979
  guard let httpResponse = serviceResponse as? HTTPURLResponse else {
2687
2980
  print("Invalid response")
2688
2981
  return
2689
2982
  }
2690
-
2983
+
2691
2984
  if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
2692
2985
  if let data = serviceData {
2693
2986
  do {
2694
2987
  if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
2695
2988
  print("Response Data: \(responseObject)")
2696
-
2989
+
2697
2990
  // Check if status is 0 and handle the error
2698
2991
  if let status = responseObject["status"] as? Int, status == 0 {
2699
2992
  let errorMessage = responseObject["message"] as? String ?? "Unknown error"
@@ -2723,39 +3016,27 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
2723
3016
  }
2724
3017
  task.resume()
2725
3018
  }
2726
-
3019
+
2727
3020
  // MARK: - Credit Card Charge Api from Saved cards If Billing info is nil and if Logged in.
2728
3021
  func paymentIntentFromShowSavedCardApi() {
2729
3022
  showLoadingIndicator()
2730
-
2731
- // var components = URLComponents()
2732
- // components.scheme = "https"
2733
- // components.host = "stage-api.stage-easymerchant.io"
2734
- // components.path = "/api/v1/charges"
2735
- //
2736
- // guard let serviceURL = components.url else {
2737
- // print("Invalid URL")
2738
- // hideLoadingIndicator()
2739
- // return
2740
- // }
2741
-
2742
- // Construct the full URL using baseURL from EnvironmentConfig and path from the endpoint
3023
+
2743
3024
  let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.creditCharges.path()
2744
-
3025
+
2745
3026
  guard let serviceURL = URL(string: fullURL) else {
2746
3027
  print("Invalid URL")
2747
3028
  hideLoadingIndicator()
2748
3029
  return
2749
3030
  }
2750
-
3031
+
2751
3032
  var request = URLRequest(url: serviceURL)
2752
3033
  request.httpMethod = "POST"
2753
3034
  request.addValue("application/json", forHTTPHeaderField: "Content-Type")
2754
-
3035
+
2755
3036
  let token = UserStoreSingleton.shared.clientToken
2756
3037
  print("Setting clientToken header: \(token ?? "None")")
2757
3038
  request.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
2758
-
3039
+
2759
3040
  // Prepare parameters for the API request
2760
3041
  let params: [String: Any] = [
2761
3042
  "name": UserStoreSingleton.shared.verificationEmail?.split(separator: "@").first ?? "",
@@ -2769,9 +3050,9 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
2769
3050
  "cvc": txtFieldCVVSingleSavedCard.text ?? "",
2770
3051
  "customer_id": selectedCard?.customerId ?? ""
2771
3052
  ]
2772
-
3053
+
2773
3054
  print(params)
2774
-
3055
+
2775
3056
  do {
2776
3057
  let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
2777
3058
  request.httpBody = jsonData
@@ -2783,30 +3064,30 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
2783
3064
  hideLoadingIndicator()
2784
3065
  return
2785
3066
  }
2786
-
3067
+
2787
3068
  let session = URLSession.shared
2788
3069
  let task = session.dataTask(with: request) { (serviceData, serviceResponse, error) in
2789
-
3070
+
2790
3071
  DispatchQueue.main.async {
2791
3072
  self.hideLoadingIndicator() // Stop loader when response is received
2792
3073
  }
2793
-
3074
+
2794
3075
  if let error = error {
2795
3076
  print("Error: \(error.localizedDescription)")
2796
3077
  return
2797
3078
  }
2798
-
3079
+
2799
3080
  guard let httpResponse = serviceResponse as? HTTPURLResponse else {
2800
3081
  print("Invalid response")
2801
3082
  return
2802
3083
  }
2803
-
3084
+
2804
3085
  if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
2805
3086
  if let data = serviceData {
2806
3087
  do {
2807
3088
  if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
2808
3089
  print("Response Data: \(responseObject)")
2809
-
3090
+
2810
3091
  // Check if status is 0 and handle the error
2811
3092
  if let status = responseObject["status"] as? Int, status == 0 {
2812
3093
  let errorMessage = responseObject["message"] as? String ?? "Unknown error"
@@ -2836,46 +3117,34 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
2836
3117
  }
2837
3118
  task.resume()
2838
3119
  }
2839
-
3120
+
2840
3121
  //MARK: - Credit Card Charge Api from Add new cards If Billing info is nil and Logged in.
2841
3122
  func paymentIntentFromAddNewCardApi(customerId: String?) {
2842
3123
  showLoadingIndicator()
2843
-
2844
- // var components = URLComponents()
2845
- // components.scheme = "https"
2846
- // components.host = "stage-api.stage-easymerchant.io"
2847
- // components.path = "/api/v1/charges"
2848
- //
2849
- // guard let serviceURL = components.url else {
2850
- // print("Invalid URL")
2851
- // hideLoadingIndicator()
2852
- // return
2853
- // }
2854
-
2855
- // Construct the full URL using baseURL from EnvironmentConfig and path from the endpoint
3124
+
2856
3125
  let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.creditCharges.path()
2857
-
3126
+
2858
3127
  guard let serviceURL = URL(string: fullURL) else {
2859
3128
  print("Invalid URL")
2860
3129
  hideLoadingIndicator()
2861
3130
  return
2862
3131
  }
2863
-
3132
+
2864
3133
  var request = URLRequest(url: serviceURL)
2865
3134
  request.httpMethod = "POST"
2866
3135
  request.addValue("application/json", forHTTPHeaderField: "Content-Type")
2867
-
3136
+
2868
3137
  let token = UserStoreSingleton.shared.clientToken
2869
3138
  print("Setting clientToken header: \(token ?? "None")")
2870
3139
  request.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
2871
-
3140
+
2872
3141
  // Get the text fields from the selected cell and trim whitespace
2873
3142
  let nameText = txtFieldNameOnCardNewCardView.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
2874
3143
  let cvvText = txtFieldCVVNewCardView.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
2875
3144
  let cardNumberText = (txtFieldCardNumberNewCardView.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "").replacingOccurrences(of: " ", with: "")
2876
3145
  let expiryText = txtFieldExpiryDateNewCardView.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
2877
-
2878
-
3146
+
3147
+
2879
3148
  // Check if any field is empty
2880
3149
  if cardNumberText.isEmpty || expiryText.isEmpty || cvvText.isEmpty || nameText.isEmpty {
2881
3150
  let alert = UIAlertController(title: "Missing Information", message: "Please fill in all card details.", preferredStyle: .alert)
@@ -2883,13 +3152,13 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
2883
3152
  self.present(alert, animated: true, completion: nil)
2884
3153
  return
2885
3154
  }
2886
-
3155
+
2887
3156
  // Validate expiry date format
2888
3157
  let expiryDateFormat = "MM/yyyy"
2889
3158
  let dateFormatter = DateFormatter()
2890
3159
  dateFormatter.dateFormat = expiryDateFormat
2891
3160
  dateFormatter.locale = Locale(identifier: "en_US_POSIX")
2892
-
3161
+
2893
3162
  // Split the expiry date and validate its format
2894
3163
  let exp = expiryText.split(separator: "/")
2895
3164
  if exp.count != 2 || exp[0].count != 2 || exp[1].count != 4 {
@@ -2898,7 +3167,7 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
2898
3167
  self.present(alert, animated: true, completion: nil)
2899
3168
  return
2900
3169
  }
2901
-
3170
+
2902
3171
  // Check if the expiry date is valid
2903
3172
  if let expiryDateObj = dateFormatter.date(from: expiryText) {
2904
3173
  // Check if the expiry date is in the past
@@ -2917,10 +3186,10 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
2917
3186
  self.present(alert, animated: true, completion: nil)
2918
3187
  return
2919
3188
  }
2920
-
2921
-
3189
+
3190
+
2922
3191
  let emailPrefix = UserStoreSingleton.shared.verificationEmail?.components(separatedBy: "@").first ?? ""
2923
-
3192
+
2924
3193
  var params: [String: Any] = [
2925
3194
  "name": nameText,
2926
3195
  "card_number": cardNumberText,
@@ -2934,21 +3203,21 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
2934
3203
  "save_card": isSavedNewCardForFuture ? 1 : 0,
2935
3204
  "email" : UserStoreSingleton.shared.verificationEmail ?? ""
2936
3205
  ]
2937
-
3206
+
2938
3207
  // Add is_default parameter if save_card is 1
2939
3208
  if isSavedNewCardForFuture {
2940
3209
  params["is_default"] = "1"
2941
3210
  }
2942
-
3211
+
2943
3212
  if let customerId = customerId {
2944
3213
  params["customer"] = customerId
2945
3214
  params["customer_id"] = customerId
2946
3215
  } else {
2947
3216
  params["username"] = emailPrefix
2948
3217
  }
2949
-
3218
+
2950
3219
  print(params)
2951
-
3220
+
2952
3221
  do {
2953
3222
  let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
2954
3223
  request.httpBody = jsonData
@@ -2960,30 +3229,30 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
2960
3229
  hideLoadingIndicator()
2961
3230
  return
2962
3231
  }
2963
-
3232
+
2964
3233
  let session = URLSession.shared
2965
3234
  let task = session.dataTask(with: request) { (serviceData, serviceResponse, error) in
2966
-
3235
+
2967
3236
  DispatchQueue.main.async {
2968
3237
  self.hideLoadingIndicator() // Stop loader when response is received
2969
3238
  }
2970
-
3239
+
2971
3240
  if let error = error {
2972
3241
  self.presentPaymentErrorVC(errorMessage: error.localizedDescription)
2973
3242
  return
2974
3243
  }
2975
-
3244
+
2976
3245
  guard let httpResponse = serviceResponse as? HTTPURLResponse else {
2977
3246
  self.presentPaymentErrorVC(errorMessage: "Invalid response")
2978
3247
  return
2979
3248
  }
2980
-
3249
+
2981
3250
  if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
2982
3251
  if let data = serviceData {
2983
3252
  do {
2984
3253
  if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
2985
3254
  print("Response Data: \(responseObject)")
2986
-
3255
+
2987
3256
  // Check if status is 0 and handle the error
2988
3257
  if let status = responseObject["status"] as? Int, status == 0 {
2989
3258
  let errorMessage = responseObject["message"] as? String ?? "Unknown error"
@@ -3013,44 +3282,32 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
3013
3282
  }
3014
3283
  task.resume()
3015
3284
  }
3016
-
3285
+
3017
3286
  //MARK: - Update Cards Api.
3018
3287
  func updateCardApi(cardID: String) {
3019
3288
  showLoadingIndicator()
3020
-
3021
- // var components = URLComponents()
3022
- // components.scheme = "https"
3023
- // components.host = "stage-api.stage-easymerchant.io"
3024
- // components.path = "/api/v1/card/\(cardID)" // Append the card_id to the path
3025
- //
3026
- // guard let serviceURL = components.url else {
3027
- // print("Invalid URL")
3028
- // hideLoadingIndicator()
3029
- // return
3030
- // }
3031
-
3032
- // Construct the full URL using baseURL from EnvironmentConfig and path from the endpoint
3289
+
3033
3290
  let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.getCards.path()+"/\(cardID)"
3034
-
3291
+
3035
3292
  guard let serviceURL = URL(string: fullURL) else {
3036
3293
  print("Invalid URL")
3037
3294
  hideLoadingIndicator()
3038
3295
  return
3039
3296
  }
3040
-
3297
+
3041
3298
  var request = URLRequest(url: serviceURL)
3042
3299
  request.httpMethod = "PUT"
3043
3300
  request.addValue("application/json", forHTTPHeaderField: "Content-Type")
3044
-
3301
+
3045
3302
  let token = UserStoreSingleton.shared.customerToken
3046
3303
  print("Setting customerToken header: \(token ?? "None")")
3047
3304
  request.addValue(token ?? "", forHTTPHeaderField: "Customer-Token")
3048
-
3305
+
3049
3306
  // Get the text fields from the selected cell and trim whitespace
3050
3307
  let nameText = txtFieldNameOnCardUpdateCardView.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
3051
3308
  let cvvText = txtFieldCVVUpdateCardView.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
3052
3309
  let expiryText = txtFieldExpireDateUpdateCardView.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
3053
-
3310
+
3054
3311
  // Check if any field is empty
3055
3312
  if expiryText.isEmpty || cvvText.isEmpty || nameText.isEmpty {
3056
3313
  let alert = UIAlertController(title: "Missing Information", message: "Please fill in all card details.", preferredStyle: .alert)
@@ -3058,13 +3315,13 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
3058
3315
  self.present(alert, animated: true, completion: nil)
3059
3316
  return
3060
3317
  }
3061
-
3318
+
3062
3319
  // Validate expiry date format
3063
3320
  let expiryDateFormat = "MM/yyyy"
3064
3321
  let dateFormatter = DateFormatter()
3065
3322
  dateFormatter.dateFormat = expiryDateFormat
3066
3323
  dateFormatter.locale = Locale(identifier: "en_US_POSIX")
3067
-
3324
+
3068
3325
  // Split the expiry date and validate its format
3069
3326
  let exp = expiryText.split(separator: "/")
3070
3327
  if exp.count != 2 || exp[0].count != 2 || exp[1].count != 4 {
@@ -3073,7 +3330,7 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
3073
3330
  self.present(alert, animated: true, completion: nil)
3074
3331
  return
3075
3332
  }
3076
-
3333
+
3077
3334
  // Check if the expiry date is valid
3078
3335
  if let expiryDateObj = dateFormatter.date(from: expiryText) {
3079
3336
  // Check if the expiry date is in the past
@@ -3092,7 +3349,7 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
3092
3349
  self.present(alert, animated: true, completion: nil)
3093
3350
  return
3094
3351
  }
3095
-
3352
+
3096
3353
  // Set up parameters with selected card details
3097
3354
  let params: [String: Any] = [
3098
3355
  "card_id": cardID,
@@ -3101,9 +3358,9 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
3101
3358
  "exp_month": expiryText.components(separatedBy: "/").first ?? "",
3102
3359
  "exp_year": expiryText.components(separatedBy: "/").last ?? "",
3103
3360
  ]
3104
-
3361
+
3105
3362
  print(params)
3106
-
3363
+
3107
3364
  do {
3108
3365
  let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
3109
3366
  request.httpBody = jsonData
@@ -3115,31 +3372,31 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
3115
3372
  hideLoadingIndicator()
3116
3373
  return
3117
3374
  }
3118
-
3375
+
3119
3376
  let session = URLSession.shared
3120
3377
  let task = session.dataTask(with: request) { (serviceData, serviceResponse, error) in
3121
-
3378
+
3122
3379
  DispatchQueue.main.async {
3123
3380
  self.hideLoadingIndicator() // Stop loader when response is received
3124
3381
  }
3125
-
3382
+
3126
3383
  if let error = error {
3127
3384
  self.presentPaymentErrorVC(errorMessage: error.localizedDescription)
3128
3385
  return
3129
3386
  }
3130
-
3387
+
3131
3388
  guard let httpResponse = serviceResponse as? HTTPURLResponse else {
3132
3389
  self.presentPaymentErrorVC(errorMessage: "Invalid response")
3133
3390
  return
3134
3391
  }
3135
-
3392
+
3136
3393
  if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
3137
3394
  if let data = serviceData {
3138
3395
  do {
3139
3396
  if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
3140
3397
  print("Response Data: \(responseObject)")
3141
3398
  DispatchQueue.main.async {
3142
-
3399
+
3143
3400
  self.viewUpdateCard.isHidden = true
3144
3401
  self.viewChangeCard.isHidden = false
3145
3402
  self.OTPView.isHidden = true
@@ -3153,9 +3410,9 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
3153
3410
  self.btnNext.isHidden = true
3154
3411
  self.btnNextHeight.constant = 0
3155
3412
  self.btnNextTopCon.constant = 8
3156
-
3413
+
3157
3414
  self.getShowCardsApi()
3158
-
3415
+
3159
3416
  self.txtFieldNameOnCardUpdateCardView.text = ""
3160
3417
  self.txtFieldExpireDateUpdateCardView.text = ""
3161
3418
  self.txtFieldCVVUpdateCardView.text = ""
@@ -3175,63 +3432,51 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
3175
3432
  }
3176
3433
  task.resume()
3177
3434
  }
3178
-
3435
+
3179
3436
  //MARK: - Delete Cards Api.
3180
3437
  func deleteCardApi(cardID: String, at index: Int) {
3181
3438
  showLoadingIndicator()
3182
-
3183
- // var components = URLComponents()
3184
- // components.scheme = "https"
3185
- // components.host = "stage-api.stage-easymerchant.io"
3186
- // components.path = "/api/v1/card/\(cardID)" // Append the card_id to the path
3187
- //
3188
- // guard let serviceURL = components.url else {
3189
- // print("Invalid URL")
3190
- // hideLoadingIndicator()
3191
- // return
3192
- // }
3193
-
3194
- // Construct the full URL using baseURL from EnvironmentConfig and path from the endpoint
3439
+
3195
3440
  let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.getCards.path()+"/\(cardID)"
3196
-
3441
+
3197
3442
  guard let serviceURL = URL(string: fullURL) else {
3198
3443
  print("Invalid URL")
3199
3444
  hideLoadingIndicator()
3200
3445
  return
3201
3446
  }
3202
-
3447
+
3203
3448
  var request = URLRequest(url: serviceURL)
3204
3449
  request.httpMethod = "DELETE"
3205
3450
  request.addValue("application/json", forHTTPHeaderField: "Content-Type")
3206
-
3451
+
3207
3452
  let token = UserStoreSingleton.shared.customerToken
3208
3453
  print("Setting customerToken header: \(token ?? "None")")
3209
3454
  request.addValue(token ?? "", forHTTPHeaderField: "Customer-Token")
3210
-
3455
+
3211
3456
  let session = URLSession.shared
3212
3457
  let task = session.dataTask(with: request) { [weak self] (serviceData, serviceResponse, error) in
3213
3458
  guard let self = self else { return }
3214
-
3459
+
3215
3460
  DispatchQueue.main.async {
3216
3461
  self.hideLoadingIndicator() // Stop loader when response is received
3217
3462
  }
3218
-
3463
+
3219
3464
  if let error = error {
3220
3465
  DispatchQueue.main.async {
3221
3466
  self.presentPaymentErrorVC(errorMessage: error.localizedDescription)
3222
3467
  }
3223
3468
  return
3224
3469
  }
3225
-
3470
+
3226
3471
  guard let httpResponse = serviceResponse as? HTTPURLResponse else {
3227
3472
  DispatchQueue.main.async {
3228
3473
  self.presentPaymentErrorVC(errorMessage: "Invalid response")
3229
3474
  }
3230
3475
  return
3231
3476
  }
3232
-
3477
+
3233
3478
  if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
3234
-
3479
+
3235
3480
  if let data = serviceData {
3236
3481
  do {
3237
3482
  // Try to parse the data as JSON
@@ -3255,7 +3500,7 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
3255
3500
  }
3256
3501
  task.resume()
3257
3502
  }
3258
-
3503
+
3259
3504
  func presentPaymentErrorVC(errorMessage: String) {
3260
3505
  DispatchQueue.main.async {
3261
3506
  if let paymentErrorVC = self.storyboard?.instantiateViewController(withIdentifier: "PaymentErrorVC") as? PaymentErrorVC {
@@ -3265,24 +3510,24 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
3265
3510
  }
3266
3511
  }
3267
3512
  }
3268
-
3513
+
3269
3514
  //MARK: - Bank Api's
3270
3515
  // MARK: - GET Show Bank Accounts API
3271
3516
  func getShowBankAccountsApi() {
3272
3517
  showLoadingIndicator()
3273
-
3518
+
3274
3519
  let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.account.path()
3275
-
3520
+
3276
3521
  guard let serviceURL = URL(string: fullURL) else {
3277
3522
  print("Invalid URL")
3278
3523
  hideLoadingIndicator()
3279
3524
  return
3280
3525
  }
3281
-
3282
- var request = URLRequest(url: serviceURL)
3283
- request.httpMethod = "GET"
3284
- request.addValue("application/json", forHTTPHeaderField: "Content-Type")
3285
-
3526
+
3527
+ var urlRequest = URLRequest(url: serviceURL)
3528
+ urlRequest.httpMethod = "GET"
3529
+ urlRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
3530
+
3286
3531
  // Retrieve and validate customer token
3287
3532
  guard let token = UserStoreSingleton.shared.customerToken, !token.isEmpty else {
3288
3533
  print("Customer token is missing or empty")
@@ -3290,66 +3535,66 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
3290
3535
  showAlert(message: "Session expired. Please log in again.")
3291
3536
  return
3292
3537
  }
3293
-
3538
+
3294
3539
  print("Setting customerToken header: \(token)")
3295
- request.addValue(token, forHTTPHeaderField: "Customer-Token")
3296
-
3540
+ urlRequest.addValue(token, forHTTPHeaderField: "Customer-Token")
3541
+
3297
3542
  let session = URLSession.shared
3298
- let task = session.dataTask(with: request) { [weak self] (data, response, error) in
3543
+ let task = session.dataTask(with: urlRequest) { [weak self] (data, response, error) in
3299
3544
  guard let self = self else { return }
3300
-
3545
+
3301
3546
  DispatchQueue.main.async {
3302
3547
  self.hideLoadingIndicator()
3303
3548
  }
3304
-
3549
+
3305
3550
  if let error = error {
3306
3551
  print("Error: \(error.localizedDescription)")
3307
3552
  return
3308
3553
  }
3309
-
3554
+
3310
3555
  if let httpResponse = response as? HTTPURLResponse {
3311
3556
  print("Status Code: \(httpResponse.statusCode)")
3312
3557
  print("Headers: \(httpResponse.allHeaderFields)")
3313
3558
  }
3314
-
3559
+
3315
3560
  guard let data = data else {
3316
3561
  print("No data received")
3317
3562
  return
3318
3563
  }
3319
-
3564
+
3320
3565
  if let responseString = String(data: data, encoding: .utf8) {
3321
3566
  print("Response Body: \(responseString)")
3322
3567
  }
3323
-
3568
+
3324
3569
  do {
3325
3570
  let decoder = JSONDecoder()
3326
3571
  let bankAccountModel = try decoder.decode(BankAccountModel.self, from: data)
3327
-
3572
+
3328
3573
  if bankAccountModel.status == true {
3329
3574
  DispatchQueue.main.async {
3330
3575
  self.bankAccounts = bankAccountModel.accounts ?? []
3331
3576
  print("Number of bank accounts: \(self.bankAccounts.count)")
3332
-
3577
+
3333
3578
  if self.bankAccounts.isEmpty {
3334
3579
  print("No bank accounts found. Please add a new one.")
3335
3580
  return
3336
3581
  }
3337
-
3582
+
3338
3583
  if let firstAccount = self.bankAccounts.first {
3339
3584
  self.selectedbankAccounts = firstAccount
3340
3585
  self.lblAccountNumberSingleAccountView.text = "****\(firstAccount.account_number_last_4 ?? "")"
3341
3586
  self.lblAccountTypeSingleAccountView.text = "Type: \(firstAccount.account_type ?? "")"
3342
3587
  }
3343
-
3588
+
3344
3589
  self.tblViewSavedBankAccounts.reloadData()
3345
-
3590
+
3346
3591
  self.viewSingleSavedAccount.isHidden = false
3347
3592
  self.viewBankFields.isHidden = true
3348
-
3593
+
3349
3594
  self.viewBtnShowSavedCards.isHidden = true
3350
3595
  self.viewBtnShowSavedCardHeight.constant = 0
3351
3596
  self.viewBtnShowSavedCardTopCon.constant = 0
3352
-
3597
+
3353
3598
  self.OTPView.isHidden = true
3354
3599
  self.emailView.isHidden = true
3355
3600
  self.viewCardFields.isHidden = true
@@ -3360,24 +3605,26 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
3360
3605
  self.btnNext.isHidden = true
3361
3606
  self.btnNextHeight.constant = 0
3362
3607
  self.btnNextTopCon.constant = 8
3363
-
3608
+
3364
3609
  self.viewNewBankAccount.isHidden = true
3365
3610
  self.viewCrypto.isHidden = true
3366
3611
  self.viewTermsAndConditions.isHidden = true
3367
3612
  self.viewChangedAccount.isHidden = true
3368
-
3613
+
3369
3614
  if self.isSelectForPayBank {
3370
3615
  self.viewTermAndConditionsSingleAccountView.isHidden = false
3371
3616
  self.btnPayNowSingleAccountView.isHidden = false
3372
- self.viewSingleAccountViewHeight.constant = 170
3617
+ // self.viewSingleAccountViewHeight.constant = 170
3373
3618
  } else {
3374
3619
  self.viewTermAndConditionsSingleAccountView.isHidden = true
3375
3620
  self.btnPayNowSingleAccountView.isHidden = true
3376
- self.viewSingleAccountViewHeight.constant = 68
3621
+ // self.viewSingleAccountViewHeight.constant = 68
3377
3622
  self.btnNext.isHidden = true
3378
3623
  self.btnNextHeight.constant = 0
3379
3624
  self.btnNextTopCon.constant = 0
3380
3625
  }
3626
+
3627
+ self.grailPayBankLinkView.isHidden = true
3381
3628
  }
3382
3629
  } else {
3383
3630
  print("Error from server: \(bankAccountModel.message ?? "Unknown error")")
@@ -3389,29 +3636,29 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
3389
3636
  print("Failed to parse response: \(error.localizedDescription)")
3390
3637
  }
3391
3638
  }
3392
-
3639
+
3393
3640
  task.resume()
3394
3641
  }
3395
-
3642
+
3396
3643
  //MARK: - Banking Account Charge Api If Billing info is nil and without Login
3397
3644
  func accountChargeApi() {
3398
3645
  showLoadingIndicator()
3399
3646
  let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.achCharge.path()
3400
-
3647
+
3401
3648
  guard let serviceURL = URL(string: fullURL) else {
3402
3649
  print("Invalid URL")
3403
3650
  hideLoadingIndicator()
3404
3651
  return
3405
3652
  }
3406
-
3653
+
3407
3654
  var request = URLRequest(url: serviceURL)
3408
3655
  request.httpMethod = "POST"
3409
3656
  request.addValue("application/json", forHTTPHeaderField: "Content-Type")
3410
-
3657
+
3411
3658
  let token = UserStoreSingleton.shared.clientToken
3412
3659
  print("Setting clientToken header: \(token ?? "None")")
3413
3660
  request.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
3414
-
3661
+
3415
3662
  let params: [String: Any] = [
3416
3663
  "name": txtFieldAccountName.text ?? "",
3417
3664
  "email": "test@gmail.com",
@@ -3424,7 +3671,7 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
3424
3671
  "payment_intent": UserStoreSingleton.shared.paymentIntent ?? "",
3425
3672
  "levelIndicator": 1,
3426
3673
  ]
3427
-
3674
+
3428
3675
  do {
3429
3676
  let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
3430
3677
  request.httpBody = jsonData
@@ -3436,30 +3683,30 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
3436
3683
  hideLoadingIndicator()
3437
3684
  return
3438
3685
  }
3439
-
3686
+
3440
3687
  let session = URLSession.shared
3441
3688
  let task = session.dataTask(with: request) { (serviceData, serviceResponse, error) in
3442
-
3689
+
3443
3690
  DispatchQueue.main.async {
3444
3691
  self.hideLoadingIndicator() // Stop loader when response is received
3445
3692
  }
3446
-
3693
+
3447
3694
  if let error = error {
3448
3695
  print("Error: \(error.localizedDescription)")
3449
3696
  return
3450
3697
  }
3451
-
3698
+
3452
3699
  guard let httpResponse = serviceResponse as? HTTPURLResponse else {
3453
3700
  print("Invalid response")
3454
3701
  return
3455
3702
  }
3456
-
3703
+
3457
3704
  if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
3458
3705
  if let data = serviceData {
3459
3706
  do {
3460
3707
  if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
3461
3708
  print("Response Data: \(responseObject)")
3462
-
3709
+
3463
3710
  // Check if status is 0 and handle the error
3464
3711
  if let status = responseObject["status"] as? Int, status == 0 {
3465
3712
  let errorMessage = responseObject["message"] as? String ?? "Unknown error"
@@ -3490,27 +3737,27 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
3490
3737
  }
3491
3738
  task.resume()
3492
3739
  }
3493
-
3740
+
3494
3741
  //MARK: - Banking Account Charge Api from Regular saved bank account if Billing info is nil
3495
3742
  func accountChargeSavedBankAccountApi() {
3496
3743
  showLoadingIndicator()
3497
-
3744
+
3498
3745
  let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.achCharge.path()
3499
-
3746
+
3500
3747
  guard let serviceURL = URL(string: fullURL) else {
3501
3748
  print("Invalid URL")
3502
3749
  hideLoadingIndicator()
3503
3750
  return
3504
3751
  }
3505
-
3752
+
3506
3753
  var request = URLRequest(url: serviceURL)
3507
3754
  request.httpMethod = "POST"
3508
3755
  request.addValue("application/json", forHTTPHeaderField: "Content-Type")
3509
-
3756
+
3510
3757
  let token = UserStoreSingleton.shared.clientToken
3511
3758
  print("Setting clientToken header: \(token ?? "None")")
3512
3759
  request.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
3513
-
3760
+
3514
3761
  let params: [String: Any] = [
3515
3762
  "account_id": selectedbankAccounts?.account_id ?? "",
3516
3763
  "customer": selectedbankAccounts?.customer_id ?? "",
@@ -3518,9 +3765,9 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
3518
3765
  "description": "Test Description",
3519
3766
  "currency": "usd",
3520
3767
  ]
3521
-
3768
+
3522
3769
  print(params)
3523
-
3770
+
3524
3771
  do {
3525
3772
  let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
3526
3773
  request.httpBody = jsonData
@@ -3532,30 +3779,30 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
3532
3779
  hideLoadingIndicator()
3533
3780
  return
3534
3781
  }
3535
-
3782
+
3536
3783
  let session = URLSession.shared
3537
3784
  let task = session.dataTask(with: request) { (serviceData, serviceResponse, error) in
3538
-
3785
+
3539
3786
  DispatchQueue.main.async {
3540
3787
  self.hideLoadingIndicator() // Stop loader when response is received
3541
3788
  }
3542
-
3789
+
3543
3790
  if let error = error {
3544
3791
  print("Error: \(error.localizedDescription)")
3545
3792
  return
3546
3793
  }
3547
-
3794
+
3548
3795
  guard let httpResponse = serviceResponse as? HTTPURLResponse else {
3549
3796
  print("Invalid response")
3550
3797
  return
3551
3798
  }
3552
-
3799
+
3553
3800
  if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
3554
3801
  if let data = serviceData {
3555
3802
  do {
3556
3803
  if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
3557
3804
  print("Response Data: \(responseObject)")
3558
-
3805
+
3559
3806
  // Check if status is 0 and handle the error
3560
3807
  if let status = responseObject["status"] as? Int, status == 0 {
3561
3808
  let errorMessage = responseObject["message"] as? String ?? "Unknown error"
@@ -3586,32 +3833,32 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
3586
3833
  }
3587
3834
  task.resume()
3588
3835
  }
3589
-
3836
+
3590
3837
  //MARK: - Banking Account Charge Api from add new account if billing info is nil and user not saved the account
3591
3838
  func accountChargeAddNewAccountApi() {
3592
3839
  showLoadingIndicator()
3593
-
3840
+
3594
3841
  let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.achCharge.path()
3595
-
3842
+
3596
3843
  guard let serviceURL = URL(string: fullURL) else {
3597
3844
  print("Invalid URL")
3598
3845
  hideLoadingIndicator()
3599
3846
  return
3600
3847
  }
3601
-
3848
+
3602
3849
  var request = URLRequest(url: serviceURL)
3603
3850
  request.httpMethod = "POST"
3604
3851
  request.addValue("application/json", forHTTPHeaderField: "Content-Type")
3605
-
3852
+
3606
3853
  let token = UserStoreSingleton.shared.clientToken
3607
3854
  print("Setting clientToken header: \(token ?? "None")")
3608
3855
  request.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
3609
-
3856
+
3610
3857
  let accountName = txtFieldAccountNameNewAccountView.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
3611
3858
  let routingNumber = txtFieldRoutingNumberNewAccountView.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
3612
3859
  let accountType = txtFieldAccountTypeNewAccountView.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
3613
3860
  let accountNumber = txtFieldAccountNumberNewAccountView.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
3614
-
3861
+
3615
3862
  let params: [String: Any] = [
3616
3863
  "name": accountName,
3617
3864
  "email": UserStoreSingleton.shared.verificationEmail ?? "",
@@ -3624,9 +3871,9 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
3624
3871
  "payment_intent": UserStoreSingleton.shared.paymentIntent ?? "",
3625
3872
  "levelIndicator": 1,
3626
3873
  ]
3627
-
3874
+
3628
3875
  print(params)
3629
-
3876
+
3630
3877
  do {
3631
3878
  let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
3632
3879
  request.httpBody = jsonData
@@ -3638,30 +3885,30 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
3638
3885
  hideLoadingIndicator()
3639
3886
  return
3640
3887
  }
3641
-
3888
+
3642
3889
  let session = URLSession.shared
3643
3890
  let task = session.dataTask(with: request) { (serviceData, serviceResponse, error) in
3644
-
3891
+
3645
3892
  DispatchQueue.main.async {
3646
3893
  self.hideLoadingIndicator() // Stop loader when response is received
3647
3894
  }
3648
-
3895
+
3649
3896
  if let error = error {
3650
3897
  print("Error: \(error.localizedDescription)")
3651
3898
  return
3652
3899
  }
3653
-
3900
+
3654
3901
  guard let httpResponse = serviceResponse as? HTTPURLResponse else {
3655
3902
  print("Invalid response")
3656
3903
  return
3657
3904
  }
3658
-
3905
+
3659
3906
  if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
3660
3907
  if let data = serviceData {
3661
3908
  do {
3662
3909
  if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
3663
3910
  print("Response Data: \(responseObject)")
3664
-
3911
+
3665
3912
  // Check if status is 0 and handle the error
3666
3913
  if let status = responseObject["status"] as? Int, status == 0 {
3667
3914
  let errorMessage = responseObject["message"] as? String ?? "Unknown error"
@@ -3692,35 +3939,35 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
3692
3939
  }
3693
3940
  task.resume()
3694
3941
  }
3695
-
3942
+
3696
3943
  //MARK: - Account Charge Api if user saved the account.
3697
3944
  func accountChargeApi(customerId: String?) {
3698
3945
  showLoadingIndicator()
3699
-
3946
+
3700
3947
  let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.achCharge.path()
3701
-
3948
+
3702
3949
  guard let serviceURL = URL(string: fullURL) else {
3703
3950
  print("Invalid URL")
3704
3951
  hideLoadingIndicator()
3705
3952
  return
3706
3953
  }
3707
-
3954
+
3708
3955
  var request = URLRequest(url: serviceURL)
3709
3956
  request.httpMethod = "POST"
3710
3957
  request.addValue("application/json", forHTTPHeaderField: "Content-Type")
3711
-
3958
+
3712
3959
  let token = UserStoreSingleton.shared.clientToken
3713
3960
  print("Setting clientToken header: \(token ?? "None")")
3714
3961
  request.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
3715
-
3962
+
3716
3963
  // Retrieve and trim text field values (removing leading and trailing whitespace)
3717
3964
  let accountName = txtFieldAccountNameNewAccountView.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
3718
3965
  let routingNumber = txtFieldRoutingNumberNewAccountView.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
3719
3966
  let accountType = txtFieldAccountTypeNewAccountView.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
3720
3967
  let accountNumber = txtFieldAccountNumberNewAccountView.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
3721
-
3968
+
3722
3969
  let emailPrefix = UserStoreSingleton.shared.verificationEmail?.components(separatedBy: "@").first ?? ""
3723
-
3970
+
3724
3971
  var params: [String: Any] = [
3725
3972
  "name": accountName,
3726
3973
  "email": UserStoreSingleton.shared.verificationEmail ?? "",
@@ -3735,15 +3982,15 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
3735
3982
  "save_account": 1,
3736
3983
  "payment_method": "ach"
3737
3984
  ]
3738
-
3985
+
3739
3986
  if let customerId = customerId {
3740
3987
  params["customer"] = customerId
3741
3988
  } else {
3742
3989
  params["username"] = emailPrefix
3743
3990
  }
3744
-
3991
+
3745
3992
  print(params)
3746
-
3993
+
3747
3994
  do {
3748
3995
  let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
3749
3996
  request.httpBody = jsonData
@@ -3755,30 +4002,30 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
3755
4002
  hideLoadingIndicator()
3756
4003
  return
3757
4004
  }
3758
-
4005
+
3759
4006
  let session = URLSession.shared
3760
4007
  let task = session.dataTask(with: request) { (serviceData, serviceResponse, error) in
3761
-
4008
+
3762
4009
  DispatchQueue.main.async {
3763
4010
  self.hideLoadingIndicator() // Stop loader when response is received
3764
4011
  }
3765
-
4012
+
3766
4013
  if let error = error {
3767
4014
  self.presentPaymentErrorVC(errorMessage: error.localizedDescription)
3768
4015
  return
3769
4016
  }
3770
-
4017
+
3771
4018
  guard let httpResponse = serviceResponse as? HTTPURLResponse else {
3772
4019
  self.presentPaymentErrorVC(errorMessage: "Invalid response")
3773
4020
  return
3774
4021
  }
3775
-
4022
+
3776
4023
  if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
3777
4024
  if let data = serviceData {
3778
4025
  do {
3779
4026
  if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
3780
4027
  print("Response Data: \(responseObject)")
3781
-
4028
+
3782
4029
  // Check if status is 0 and handle the error
3783
4030
  if let status = responseObject["status"] as? Int, status == 0 {
3784
4031
  let errorMessage = responseObject["message"] as? String ?? "Unknown error"
@@ -3808,51 +4055,51 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
3808
4055
  }
3809
4056
  task.resume()
3810
4057
  }
3811
-
4058
+
3812
4059
  //MARK: - Delete Bank Account Api.
3813
4060
  func deleteAccountApi(accountID: String, at index: Int) {
3814
4061
  showLoadingIndicator()
3815
-
4062
+
3816
4063
  let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.achCharge.path()+"/\(accountID)"
3817
-
4064
+
3818
4065
  guard let serviceURL = URL(string: fullURL) else {
3819
4066
  print("Invalid URL")
3820
4067
  hideLoadingIndicator()
3821
4068
  return
3822
4069
  }
3823
-
4070
+
3824
4071
  var request = URLRequest(url: serviceURL)
3825
4072
  request.httpMethod = "DELETE"
3826
4073
  request.addValue("application/json", forHTTPHeaderField: "Content-Type")
3827
-
4074
+
3828
4075
  let token = UserStoreSingleton.shared.customerToken
3829
4076
  print("Setting customerToken header: \(token ?? "None")")
3830
4077
  request.addValue(token ?? "", forHTTPHeaderField: "Customer-Token")
3831
-
4078
+
3832
4079
  let session = URLSession.shared
3833
4080
  let task = session.dataTask(with: request) { [weak self] (serviceData, serviceResponse, error) in
3834
4081
  guard let self = self else { return }
3835
-
4082
+
3836
4083
  DispatchQueue.main.async {
3837
4084
  self.hideLoadingIndicator() // Stop loader when response is received
3838
4085
  }
3839
-
4086
+
3840
4087
  if let error = error {
3841
4088
  DispatchQueue.main.async {
3842
4089
  self.presentPaymentErrorVC(errorMessage: error.localizedDescription)
3843
4090
  }
3844
4091
  return
3845
4092
  }
3846
-
4093
+
3847
4094
  guard let httpResponse = serviceResponse as? HTTPURLResponse else {
3848
4095
  DispatchQueue.main.async {
3849
4096
  self.presentPaymentErrorVC(errorMessage: "Invalid response")
3850
4097
  }
3851
4098
  return
3852
4099
  }
3853
-
4100
+
3854
4101
  if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
3855
-
4102
+
3856
4103
  if let data = serviceData {
3857
4104
  do {
3858
4105
  // Try to parse the data as JSON
@@ -3877,27 +4124,27 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
3877
4124
  }
3878
4125
  task.resume()
3879
4126
  }
3880
-
4127
+
3881
4128
  // MARK: - Crypto Charge API
3882
4129
  func cryptoChargeApi() {
3883
4130
  showLoadingIndicator()
3884
-
4131
+
3885
4132
  let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.charges.path()
3886
-
4133
+
3887
4134
  guard let serviceURL = URL(string: fullURL) else {
3888
4135
  print("Invalid URL")
3889
4136
  hideLoadingIndicator()
3890
4137
  return
3891
4138
  }
3892
-
4139
+
3893
4140
  var request = URLRequest(url: serviceURL)
3894
4141
  request.httpMethod = "POST"
3895
4142
  request.addValue("application/json", forHTTPHeaderField: "Content-Type")
3896
-
4143
+
3897
4144
  let token = UserStoreSingleton.shared.clientToken
3898
4145
  print("Setting clientToken header: \(token ?? "None")")
3899
4146
  request.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
3900
-
4147
+
3901
4148
  let params: [String: Any] = [
3902
4149
  "name": UserStoreSingleton.shared.merchantName ?? "",
3903
4150
  "email": UserStoreSingleton.shared.merchantEmail ?? "",
@@ -3908,9 +4155,9 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
3908
4155
  "success_url": "https://stage-api.stage-easymerchant.io/webhook/crypto",
3909
4156
  "callback_url": "https://stage-api.stage-easymerchant.io/webhook/crypto"
3910
4157
  ]
3911
-
4158
+
3912
4159
  print(params)
3913
-
4160
+
3914
4161
  do {
3915
4162
  let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
3916
4163
  request.httpBody = jsonData
@@ -3922,30 +4169,30 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
3922
4169
  hideLoadingIndicator()
3923
4170
  return
3924
4171
  }
3925
-
4172
+
3926
4173
  let session = URLSession.shared
3927
4174
  let task = session.dataTask(with: request) { (serviceData, serviceResponse, error) in
3928
-
4175
+
3929
4176
  DispatchQueue.main.async {
3930
4177
  self.hideLoadingIndicator() // Stop loader when response is received
3931
4178
  }
3932
-
4179
+
3933
4180
  if let error = error {
3934
4181
  print("Error: \(error.localizedDescription)")
3935
4182
  return
3936
4183
  }
3937
-
4184
+
3938
4185
  guard let httpResponse = serviceResponse as? HTTPURLResponse else {
3939
4186
  print("Invalid response")
3940
4187
  return
3941
4188
  }
3942
-
4189
+
3943
4190
  if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
3944
4191
  if let data = serviceData {
3945
4192
  do {
3946
4193
  if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
3947
4194
  print("Response Data: \(responseObject)")
3948
-
4195
+
3949
4196
  // Check if status is 0 and handle the error
3950
4197
  if let status = responseObject["status"] as? Int, status == 0 {
3951
4198
  let errorMessage = responseObject["message"] as? String ?? "Unknown error"
@@ -3956,18 +4203,18 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
3956
4203
  self.chargeId = chargeId
3957
4204
  print("Charge ID: \(chargeId)")
3958
4205
  }
3959
-
4206
+
3960
4207
  // Extract chain_invoice address and lightning invoice fields
3961
4208
  if let data = responseObject["data"] as? [String: Any] {
3962
4209
  self.chainInvoiceAddress = (data["chain_invoice"] as? [String: Any])?["address"] as? String
3963
4210
  self.lightningURI = (data["lightning_invoice"] as? [String: Any])?["payreq"] as? String
3964
-
4211
+
3965
4212
  // Default to OnChain QR code initially
3966
4213
  DispatchQueue.main.async {
3967
4214
  if let address = self.chainInvoiceAddress {
3968
4215
  self.qrImageView.image = self.generateQRCode(from: address)
3969
4216
  self.lblBTCAddress.text = "BTC Address: \(address)"
3970
-
4217
+
3971
4218
  // Extract the amount from the URI
3972
4219
  if let uri = data["uri"] as? String {
3973
4220
  if let amountString = self.extractAmount(from: uri) {
@@ -3979,7 +4226,7 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
3979
4226
  }
3980
4227
  }
3981
4228
  }
3982
-
4229
+
3983
4230
  self.viewCryptoQRCode.isHidden = false
3984
4231
  self.viewCryptoTryAgain.isHidden = true
3985
4232
  }
@@ -4002,60 +4249,60 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
4002
4249
  }
4003
4250
  task.resume()
4004
4251
  }
4005
-
4252
+
4006
4253
  // Helper function to extract the amount from the URI
4007
4254
  func extractAmount(from uri: String) -> String? {
4008
4255
  let regex = try? NSRegularExpression(pattern: "amount=([\\d.]+)", options: [])
4009
4256
  let matches = regex?.matches(in: uri, options: [], range: NSRange(location: 0, length: uri.count))
4010
-
4257
+
4011
4258
  if let match = matches?.first, let amountRange = Range(match.range(at: 1), in: uri) {
4012
4259
  return String(uri[amountRange])
4013
4260
  }
4014
4261
  return nil
4015
4262
  }
4016
-
4263
+
4017
4264
  //MARK: - GET Crypto Payment Info API
4018
4265
  func getCryptoPaymentInfoApi(chargeId: String) {
4019
-
4266
+
4020
4267
  let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.charges.path()+"/\(chargeId)"
4021
-
4268
+
4022
4269
  guard let serviceURL = URL(string: fullURL) else {
4023
4270
  print("Invalid URL")
4024
4271
  hideLoadingIndicator()
4025
4272
  return
4026
4273
  }
4027
-
4274
+
4028
4275
  var request = URLRequest(url: serviceURL)
4029
4276
  request.httpMethod = "GET"
4030
4277
  request.addValue("application/json", forHTTPHeaderField: "Content-Type")
4031
-
4278
+
4032
4279
  let token = UserStoreSingleton.shared.clientToken
4033
4280
  print("Setting clientToken header: \(token ?? "None")")
4034
4281
  request.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
4035
-
4282
+
4036
4283
  let session = URLSession.shared
4037
4284
  let task = session.dataTask(with: request) { [weak self] (data, response, error) in
4038
4285
  guard let self = self else { return }
4039
-
4286
+
4040
4287
  if let error = error {
4041
4288
  print("Error: \(error.localizedDescription)")
4042
4289
  return
4043
4290
  }
4044
-
4291
+
4045
4292
  guard let data = data else { return }
4046
-
4293
+
4047
4294
  do {
4048
4295
  if let jsonResponse = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
4049
4296
  let responseData = jsonResponse["data"] as? [String: Any] {
4050
-
4297
+
4051
4298
  // Extract required data
4052
4299
  let amount = responseData["amount"] as? String ?? ""
4053
4300
  let dateCreated = responseData["date_created"] as? String ?? ""
4054
4301
  let status = responseData["status"] as? String ?? ""
4055
4302
  let transactionId = responseData["transaction_id"] as? String ?? ""
4056
-
4303
+
4057
4304
  print("Amount: \(amount), Date Created: \(dateCreated), Status: \(status), Transaction ID: \(transactionId)")
4058
-
4305
+
4059
4306
  // Check if the status is "completed"
4060
4307
  if status.lowercased() == "completed" {
4061
4308
  DispatchQueue.main.async {
@@ -4070,7 +4317,7 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
4070
4317
  }
4071
4318
  task.resume()
4072
4319
  }
4073
-
4320
+
4074
4321
  //MARK: - Navigation to PaymentDoneVC
4075
4322
  func navigateToPaymentDoneVC(amount: String, dateCreated: String, transactionId: String) {
4076
4323
  if let paymentDoneVC = storyboard?.instantiateViewController(withIdentifier: "PaymentDoneVC") as? PaymentDoneVC {
@@ -4082,22 +4329,22 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
4082
4329
  self.navigationController?.pushViewController(paymentDoneVC, animated: true)
4083
4330
  }
4084
4331
  }
4085
-
4332
+
4086
4333
  }
4087
4334
 
4088
4335
  //MARK: - Collection View.
4089
4336
  extension PaymentInfoVC: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
4090
-
4337
+
4091
4338
  public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
4092
4339
  return arrPaymentMethods.count
4093
4340
  }
4094
-
4341
+
4095
4342
  func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
4096
4343
  let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "PaymentInformationCVC", for: indexPath) as! PaymentInformationCVC
4097
4344
  cell.setUI()
4098
4345
  cell.imgVwPayments.image = UIImage.init(systemName: arrPaymentMethods[indexPath.row].image)
4099
4346
  cell.lblPayment.text = arrPaymentMethods[indexPath.row].name
4100
-
4347
+
4101
4348
  // Update border color based on selection
4102
4349
  if selectedIndex == indexPath {
4103
4350
  if let primaryBackGroundColor = UserStoreSingleton.shared.primary_btn_bg_col,
@@ -4111,10 +4358,10 @@ extension PaymentInfoVC: UICollectionViewDelegate, UICollectionViewDataSource, U
4111
4358
  cell.imgVwPayments.tintColor = .systemGray
4112
4359
  cell.lblPayment.textColor = .systemGray
4113
4360
  }
4114
-
4361
+
4115
4362
  return cell
4116
4363
  }
4117
-
4364
+
4118
4365
  func loadPaymentMethods() {
4119
4366
  if let savedMethods = UserStoreSingleton.shared.paymentMethods {
4120
4367
  arrPaymentMethods = savedMethods.map { method in
@@ -4134,7 +4381,7 @@ extension PaymentInfoVC: UICollectionViewDelegate, UICollectionViewDataSource, U
4134
4381
  return PaymentsData(name: method, image: imageName)
4135
4382
  }
4136
4383
  }
4137
-
4384
+
4138
4385
  DispatchQueue.main.async {
4139
4386
  if !self.arrPaymentMethods.isEmpty {
4140
4387
  self.selectedIndex = IndexPath(row: 0, section: 0)
@@ -4145,19 +4392,19 @@ extension PaymentInfoVC: UICollectionViewDelegate, UICollectionViewDataSource, U
4145
4392
  self.collVwPayment.reloadData()
4146
4393
  self.collVwPayment.layoutIfNeeded()
4147
4394
  }
4148
-
4395
+
4149
4396
  }
4150
-
4397
+
4151
4398
  func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
4152
4399
  selectedIndex = indexPath
4153
4400
  let method = arrPaymentMethods[indexPath.row].name
4154
4401
  updatePaymentMethodUI(for: method)
4155
4402
  collectionView.reloadData()
4156
4403
  }
4157
-
4404
+
4158
4405
  func updatePaymentMethodUI(for method: String) {
4159
4406
  selectedPaymentMethod = method
4160
-
4407
+
4161
4408
  switch method {
4162
4409
  case "Card":
4163
4410
  if UserStoreSingleton.shared.isLoggedIn == false {
@@ -4181,6 +4428,7 @@ extension PaymentInfoVC: UICollectionViewDelegate, UICollectionViewDataSource, U
4181
4428
  self.viewTermsAndConditions.isHidden = true
4182
4429
  self.viewCrypto.isHidden = true
4183
4430
  self.grailPayBankLinkView.isHidden = true
4431
+ self.viewAddNewGrailPayAccount.isHidden = true
4184
4432
  } else {
4185
4433
  getShowCardsApi()
4186
4434
  self.viewSingleSavedAccount.isHidden = true
@@ -4203,8 +4451,9 @@ extension PaymentInfoVC: UICollectionViewDelegate, UICollectionViewDataSource, U
4203
4451
  self.viewNewBankAccount.isHidden = true
4204
4452
  self.viewCrypto.isHidden = true
4205
4453
  self.grailPayBankLinkView.isHidden = true
4454
+ self.viewAddNewGrailPayAccount.isHidden = true
4206
4455
  }
4207
-
4456
+
4208
4457
  case "Bank":
4209
4458
  if UserStoreSingleton.shared.isLoggedIn == false {
4210
4459
  // self.viewBankFields.isHidden = false
@@ -4225,12 +4474,13 @@ extension PaymentInfoVC: UICollectionViewDelegate, UICollectionViewDataSource, U
4225
4474
  // self.viewTermsAndConditions.isHidden = false
4226
4475
  self.viewCrypto.isHidden = true
4227
4476
  self.viewSingleSavedAccount.isHidden = true
4228
-
4477
+
4229
4478
  if request?.authenticatedACH == false {
4230
4479
  self.viewBankFields.isHidden = false
4231
4480
  self.viewTermsAndConditions.isHidden = false
4232
4481
  self.grailPayBankLinkView.isHidden = true
4233
4482
  self.viewGrailPayAbandon.isHidden = true
4483
+
4234
4484
  } else {
4235
4485
  self.viewBankFields.isHidden = true
4236
4486
  self.viewTermsAndConditions.isHidden = true
@@ -4239,15 +4489,16 @@ extension PaymentInfoVC: UICollectionViewDelegate, UICollectionViewDataSource, U
4239
4489
  self.btnNextHeight.constant = 0
4240
4490
  self.btnNextTopCon.constant = 0
4241
4491
  self.viewGrailPayAbandon.isHidden = true
4242
-
4492
+
4243
4493
  if isBankAccountConnected {
4244
4494
  self.viewBtnShowSavedCards.isHidden = true
4245
4495
  self.btnShowSavedCard.isHidden = true
4246
4496
  self.viewBtnShowSavedCardHeight.constant = 0
4247
4497
  self.viewBtnShowSavedCardTopCon.constant = 0
4248
4498
  }
4499
+
4249
4500
  }
4250
-
4501
+
4251
4502
  } else {
4252
4503
  if request?.authenticatedACH == true {
4253
4504
  self.viewBankFields.isHidden = true
@@ -4257,7 +4508,7 @@ extension PaymentInfoVC: UICollectionViewDelegate, UICollectionViewDataSource, U
4257
4508
  self.btnNextHeight.constant = 0
4258
4509
  self.btnNextTopCon.constant = 0
4259
4510
  self.viewGrailPayAbandon.isHidden = true
4260
-
4511
+
4261
4512
  if isBankAccountConnected {
4262
4513
  self.viewBtnShowSavedCards.isHidden = true
4263
4514
  self.btnShowSavedCard.isHidden = true
@@ -4265,15 +4516,15 @@ extension PaymentInfoVC: UICollectionViewDelegate, UICollectionViewDataSource, U
4265
4516
  self.viewBtnShowSavedCardTopCon.constant = 0
4266
4517
  }
4267
4518
  }
4268
-
4519
+
4269
4520
  getShowBankAccountsApi()
4270
4521
  }
4271
-
4522
+
4272
4523
  case "Crypto":
4273
4524
  self.cryptoChargeApi()
4274
4525
  self.startCryptoTimer()
4275
4526
  self.totalTimeInSeconds = 300
4276
-
4527
+
4277
4528
  self.viewCrypto.isHidden = false
4278
4529
  self.viewQrCodeHeight.constant = 440
4279
4530
  self.viewCryptoHeight.constant = 440
@@ -4295,18 +4546,20 @@ extension PaymentInfoVC: UICollectionViewDelegate, UICollectionViewDataSource, U
4295
4546
  self.viewTermsAndConditions.isHidden = true
4296
4547
  self.viewChangedAccount.isHidden = true
4297
4548
  self.viewNewBankAccount.isHidden = true
4298
-
4549
+
4299
4550
  self.grailPayBankLinkView.isHidden = true
4300
-
4551
+ self.viewAddNewGrailPayAccount.isHidden = true
4552
+ self.viewAbandonGrailPayNewAccountView.isHidden = true
4553
+
4301
4554
  default:
4302
4555
  break
4303
4556
  }
4304
4557
  }
4305
-
4558
+
4306
4559
  func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
4307
4560
  return CGSize(width: 100, height: 70)
4308
4561
  }
4309
-
4562
+
4310
4563
  }
4311
4564
 
4312
4565
  //MARK: - Table View
@@ -4316,7 +4569,6 @@ extension PaymentInfoVC: UITableViewDelegate, UITableViewDataSource {
4316
4569
  return savedCards.count + 1
4317
4570
  }
4318
4571
  else if tableView == tblViewSavedBankAccounts {
4319
- print("Number of bank accounts: \(bankAccounts.count)")
4320
4572
  return bankAccounts.count + 1
4321
4573
  }
4322
4574
  else if tableView == tblViewAccountTypes {
@@ -4327,7 +4579,7 @@ extension PaymentInfoVC: UITableViewDelegate, UITableViewDataSource {
4327
4579
  }
4328
4580
  return 0
4329
4581
  }
4330
-
4582
+
4331
4583
  func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
4332
4584
  if tableView == tblViewSavedCardsList {
4333
4585
  let cell = tableView.dequeueReusableCell(withIdentifier: "SavedCardsTVC", for: indexPath) as! SavedCardsTVC
@@ -4336,14 +4588,14 @@ extension PaymentInfoVC: UITableViewDelegate, UITableViewDataSource {
4336
4588
  cell.btnThreeDot.removeTarget(nil, action: nil, for: .allEvents)
4337
4589
  cell.btnRemoveCard.removeTarget(nil, action: nil, for: .allEvents)
4338
4590
  cell.btnUpdateCard.removeTarget(nil, action: nil, for: .allEvents)
4339
-
4591
+
4340
4592
  // Check if it's the "Add New Card" cell
4341
4593
  if indexPath.row == savedCards.count {
4342
4594
  configureAddNewCardCell(cell)
4343
4595
  } else {
4344
4596
  configureSavedCardCell(cell, at: indexPath)
4345
4597
  }
4346
-
4598
+
4347
4599
  return cell
4348
4600
  }
4349
4601
  else if tableView == tblViewSavedBankAccounts {
@@ -4352,7 +4604,7 @@ extension PaymentInfoVC: UITableViewDelegate, UITableViewDataSource {
4352
4604
  cell.btnSelectAccount.removeTarget(nil, action: nil, for: .allEvents)
4353
4605
  cell.btnThreeDotBank.removeTarget(nil, action: nil, for: .allEvents)
4354
4606
  cell.btnRemoveBankAccount.removeTarget(nil, action: nil, for: .allEvents)
4355
-
4607
+
4356
4608
  if indexPath.row == bankAccounts.count {
4357
4609
  print("Showing 'Add New Account' cell")
4358
4610
  configureAddNewAccountCell(cell)
@@ -4360,7 +4612,7 @@ extension PaymentInfoVC: UITableViewDelegate, UITableViewDataSource {
4360
4612
  print("Configuring saved account cell at row: \(indexPath.row)")
4361
4613
  configureSavedAccountCell(cell, at: indexPath)
4362
4614
  }
4363
-
4615
+
4364
4616
  return cell
4365
4617
  }
4366
4618
  else if tableView == tblViewAccountTypes {
@@ -4373,10 +4625,10 @@ extension PaymentInfoVC: UITableViewDelegate, UITableViewDataSource {
4373
4625
  cell.lblAccountType.text = arrAccountType[indexPath.row]
4374
4626
  return cell
4375
4627
  }
4376
-
4628
+
4377
4629
  return UITableViewCell()
4378
4630
  }
4379
-
4631
+
4380
4632
  //Card
4381
4633
  func configureAddNewCardCell(_ cell: SavedCardsTVC) {
4382
4634
  cell.lblCardNumber.text = "New Card"
@@ -4387,11 +4639,11 @@ extension PaymentInfoVC: UITableViewDelegate, UITableViewDataSource {
4387
4639
  cell.btnSelectCard.tag = savedCards.count
4388
4640
  cell.btnSelectCard.addTarget(self, action: #selector(addNewCardTapped(_:)), for: .touchUpInside)
4389
4641
  cell.viewBtnRemoveUpdate.isHidden = true
4390
-
4642
+
4391
4643
  cell.imgViewCardWidth.constant = 0
4392
4644
  cell.imgViewCardRightCon.constant = 0
4393
4645
  }
4394
-
4646
+
4395
4647
  func configureSavedCardCell(_ cell: SavedCardsTVC, at indexPath: IndexPath) {
4396
4648
  let card = savedCards[indexPath.row]
4397
4649
  cell.lblCardNumber.text = "****\(card.ccLast4)"
@@ -4399,33 +4651,33 @@ extension PaymentInfoVC: UITableViewDelegate, UITableViewDataSource {
4399
4651
  cell.lblExpireDate.text = "Expiry: \(card.ccValidThru)"
4400
4652
  cell.btnThreeDot.isHidden = false
4401
4653
  cell.imgViewCard.isHidden = false
4402
-
4654
+
4403
4655
  // Reset constraints for regular saved card cells
4404
4656
  cell.imgViewCardWidth.constant = 40 // Set to the original width constraint value
4405
4657
  cell.imgViewCardRightCon.constant = 16 // Set to the original right constraint value
4406
-
4658
+
4407
4659
  // Update button image based on the selected state
4408
4660
  let isSelected = (selectedCardIndex == indexPath.row)
4409
4661
  let imageName = isSelected ? "circle.inset.filled" : "circle"
4410
4662
  cell.btnSelectCard.setImage(UIImage(systemName: imageName), for: .normal)
4411
-
4663
+
4412
4664
  // Set up actions for buttons
4413
4665
  cell.btnSelectCard.tag = indexPath.row
4414
4666
  cell.btnSelectCard.addTarget(self, action: #selector(btnSelectCardTapped(_:)), for: .touchUpInside)
4415
-
4667
+
4416
4668
  cell.btnThreeDot.tag = indexPath.row
4417
4669
  cell.btnThreeDot.addTarget(self, action: #selector(btnThreeDotTapped(_:)), for: .touchUpInside)
4418
-
4670
+
4419
4671
  cell.btnRemoveCard.tag = indexPath.row
4420
4672
  cell.btnRemoveCard.addTarget(self, action: #selector(btnRemoveCardTapped(_:)), for: .touchUpInside)
4421
-
4673
+
4422
4674
  cell.btnUpdateCard.tag = indexPath.row
4423
4675
  cell.btnUpdateCard.addTarget(self, action: #selector(btnUpdateCardTapped(_:)), for: .touchUpInside)
4424
-
4676
+
4425
4677
  // Hide or show the remove/update view based on cell state
4426
4678
  cell.viewBtnRemoveUpdate.isHidden = !cell.isRemoveUpdateViewVisible
4427
4679
  }
4428
-
4680
+
4429
4681
  //Bank
4430
4682
  func configureAddNewAccountCell(_ cell: SavedAccountTVC) {
4431
4683
  cell.lblAccountNumber.text = "New Account"
@@ -4436,11 +4688,11 @@ extension PaymentInfoVC: UITableViewDelegate, UITableViewDataSource {
4436
4688
  cell.btnSelectAccount.tag = bankAccounts.count
4437
4689
  cell.btnSelectAccount.addTarget(self, action: #selector(addNewBankAccountTapped(_:)), for: .touchUpInside)
4438
4690
  cell.viewBtnRemove.isHidden = true
4439
-
4691
+
4440
4692
  cell.imgViewBankWidth.constant = 0
4441
4693
  cell.imgViewBankRightCon.constant = 0
4442
4694
  }
4443
-
4695
+
4444
4696
  func configureSavedAccountCell(_ cell: SavedAccountTVC, at indexPath: IndexPath) {
4445
4697
  let bank = bankAccounts[indexPath.row]
4446
4698
  cell.lblAccountNumber.text = "****\(bank.account_number_last_4 ?? "")"
@@ -4448,30 +4700,30 @@ extension PaymentInfoVC: UITableViewDelegate, UITableViewDataSource {
4448
4700
  cell.lblAccountType.text = "Type: \(bank.account_type ?? "")"
4449
4701
  cell.btnThreeDotBank.isHidden = false
4450
4702
  cell.imgViewBank.isHidden = false
4451
-
4703
+
4452
4704
  // Reset constraints for regular saved card cells
4453
4705
  cell.imgViewBankWidth.constant = 40 // Set to the original width constraint value
4454
4706
  cell.imgViewBankRightCon.constant = 16 // Set to the original right constraint value
4455
-
4707
+
4456
4708
  // Update button image based on the selected state
4457
4709
  let isSelected = (selectedBankIndex == indexPath.row)
4458
4710
  let imageName = isSelected ? "circle.inset.filled" : "circle"
4459
4711
  cell.btnSelectAccount.setImage(UIImage(systemName: imageName), for: .normal)
4460
-
4712
+
4461
4713
  // Set up actions for buttons
4462
4714
  cell.btnSelectAccount.tag = indexPath.row
4463
4715
  cell.btnSelectAccount.addTarget(self, action: #selector(btnSelectAccountTapped(_:)), for: .touchUpInside)
4464
-
4716
+
4465
4717
  cell.btnThreeDotBank.tag = indexPath.row
4466
4718
  cell.btnThreeDotBank.addTarget(self, action: #selector(btnThreeDotBankTapped(_:)), for: .touchUpInside)
4467
-
4719
+
4468
4720
  cell.btnRemoveBankAccount.tag = indexPath.row
4469
4721
  cell.btnRemoveBankAccount.addTarget(self, action: #selector(btnRemoveBankAccountTapped(_:)), for: .touchUpInside)
4470
-
4722
+
4471
4723
  // Hide or show the remove/update view based on cell state
4472
4724
  cell.viewBtnRemove.isHidden = !cell.isRemoveUpdateViewVisible
4473
4725
  }
4474
-
4726
+
4475
4727
  func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
4476
4728
  if tableView == tblViewAccountTypes {
4477
4729
  return 45
@@ -4483,7 +4735,7 @@ extension PaymentInfoVC: UITableViewDelegate, UITableViewDataSource {
4483
4735
  return 80
4484
4736
  }
4485
4737
  }
4486
-
4738
+
4487
4739
  func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
4488
4740
  if tableView == tblViewAccountTypes {
4489
4741
  let selectedAccountType = arrAccountType[indexPath.row]
@@ -4496,7 +4748,7 @@ extension PaymentInfoVC: UITableViewDelegate, UITableViewDataSource {
4496
4748
  viewAccountTypeNewAccountView.isHidden = true // Hide the table view after selection
4497
4749
  }
4498
4750
  }
4499
-
4751
+
4500
4752
  @objc func btnSelectCardTapped(_ sender: UIButton) {
4501
4753
  let index = sender.tag
4502
4754
  // Store the index of the previously selected card
@@ -4518,12 +4770,12 @@ extension PaymentInfoVC: UITableViewDelegate, UITableViewDataSource {
4518
4770
  // Reload the newly selected row to update its state
4519
4771
  tblViewSavedCardsList.reloadRows(at: [IndexPath(row: index, section: 0)], with: .automatic)
4520
4772
  }
4521
-
4773
+
4522
4774
  private func updateSingleCardView(for index: Int) {
4523
4775
  let selectedCard = savedCards[index]
4524
4776
  lblCardNumberSigleSavedCard.text = "****\(selectedCard.ccLast4)"
4525
4777
  lblExpireDateSingelSavedCard.text = "Expiry: \(selectedCard.ccValidThru)"
4526
-
4778
+
4527
4779
  if isSelectForPay {
4528
4780
  viewTxtFieldCVVSingleCard.isHidden = false
4529
4781
  btnPayNowSingleCard.isHidden = false
@@ -4534,7 +4786,7 @@ extension PaymentInfoVC: UITableViewDelegate, UITableViewDataSource {
4534
4786
  viewSingleSavedCardHeight.constant = 60
4535
4787
  }
4536
4788
  }
4537
-
4789
+
4538
4790
  @objc func btnThreeDotTapped(_ sender: UIButton) {
4539
4791
  let index = sender.tag
4540
4792
  guard let cell = tblViewSavedCardsList.cellForRow(at: IndexPath(row: index, section: 0)) as? SavedCardsTVC else {
@@ -4544,13 +4796,13 @@ extension PaymentInfoVC: UITableViewDelegate, UITableViewDataSource {
4544
4796
  cell.isRemoveUpdateViewVisible.toggle()
4545
4797
  cell.viewBtnRemoveUpdate.isHidden = !cell.isRemoveUpdateViewVisible
4546
4798
  }
4547
-
4799
+
4548
4800
  @objc func btnRemoveCardTapped(_ sender: UIButton) {
4549
4801
  let index = sender.tag
4550
-
4802
+
4551
4803
  let selectedCard = savedCards[index]
4552
4804
  selectedCardID = selectedCard.cardId
4553
-
4805
+
4554
4806
  let alertController = UIAlertController(
4555
4807
  title: "Confirm Deletion",
4556
4808
  message: "Are you sure you want to delete this card?",
@@ -4566,15 +4818,15 @@ extension PaymentInfoVC: UITableViewDelegate, UITableViewDataSource {
4566
4818
  alertController.addAction(yesAction)
4567
4819
  present(alertController, animated: true, completion: nil)
4568
4820
  }
4569
-
4821
+
4570
4822
  @objc func btnUpdateCardTapped(_ sender: UIButton) {
4571
4823
  let index = sender.tag
4572
4824
  let selectedCard = savedCards[index]
4573
4825
  lblCardNumberUpdateCardView.text = "****\(selectedCard.ccLast4)"
4574
4826
  lblExpireDateUpdateCardView.text = "Expiry: \(selectedCard.ccValidThru)"
4575
-
4827
+
4576
4828
  selectedCardID = selectedCard.cardId
4577
-
4829
+
4578
4830
  viewUpdateCard.isHidden = false
4579
4831
  // Adjust view visibility
4580
4832
  viewBtnShowSavedCards.isHidden = true
@@ -4588,17 +4840,17 @@ extension PaymentInfoVC: UITableViewDelegate, UITableViewDataSource {
4588
4840
  viewAddNewCard.isHidden = true
4589
4841
  viewSingleSavedCard.isHidden = true
4590
4842
  }
4591
-
4843
+
4592
4844
  @objc func addNewCardTapped(_ sender: UIButton) {
4593
4845
  // Make the Add New Card view visible
4594
4846
  viewAddNewCard.isHidden = false
4595
-
4847
+
4596
4848
  // Hide other conflicting views to prevent UI overlap
4597
4849
  viewSingleSavedCard.isHidden = true
4598
4850
  viewChangeCard.isHidden = true
4599
4851
  viewUpdateCard.isHidden = true
4600
4852
  viewCardFields.isHidden = true
4601
-
4853
+
4602
4854
  // Adjust visibility of any other related views if necessary
4603
4855
  viewBtnShowSavedCards.isHidden = true
4604
4856
  viewBtnShowSavedCardHeight.constant = 0
@@ -4607,36 +4859,62 @@ extension PaymentInfoVC: UITableViewDelegate, UITableViewDataSource {
4607
4859
  btnNextHeight.constant = 0
4608
4860
  btnNextTopCon.constant = 8
4609
4861
  }
4610
-
4862
+
4611
4863
  //Bank Cell
4612
4864
  @objc func addNewBankAccountTapped(_ sender: UIButton) {
4613
- self.viewNewBankAccount.isHidden = false
4614
- self.viewSingleSavedAccount.isHidden = true
4615
- self.viewBankFields.isHidden = true
4616
- self.viewBtnShowSavedCards.isHidden = true
4617
- self.viewBtnShowSavedCardHeight.constant = 0
4618
- self.viewBtnShowSavedCardTopCon.constant = 0
4619
- self.OTPView.isHidden = true
4620
- self.emailView.isHidden = true
4621
- self.viewCardFields.isHidden = true
4622
- self.viewSingleSavedCard.isHidden = true
4623
- self.viewChangeCard.isHidden = true
4624
- self.viewUpdateCard.isHidden = true
4625
- self.viewAddNewCard.isHidden = true
4626
- self.btnNext.isHidden = true
4627
- self.btnNextHeight.constant = 0
4628
- self.btnNextTopCon.constant = 8
4629
- self.viewChangedAccount.isHidden = true
4865
+ if request.authenticatedACH == true {
4866
+ self.viewAddNewGrailPayAccount.isHidden = false
4867
+
4868
+ self.viewNewBankAccount.isHidden = true
4869
+ self.viewSingleSavedAccount.isHidden = true
4870
+ self.viewBankFields.isHidden = true
4871
+ self.viewBtnShowSavedCards.isHidden = true
4872
+ self.viewBtnShowSavedCardHeight.constant = 0
4873
+ self.viewBtnShowSavedCardTopCon.constant = 0
4874
+ self.OTPView.isHidden = true
4875
+ self.emailView.isHidden = true
4876
+ self.viewCardFields.isHidden = true
4877
+ self.viewSingleSavedCard.isHidden = true
4878
+ self.viewChangeCard.isHidden = true
4879
+ self.viewUpdateCard.isHidden = true
4880
+ self.viewAddNewCard.isHidden = true
4881
+ self.btnNext.isHidden = true
4882
+ self.btnNextHeight.constant = 0
4883
+ self.btnNextTopCon.constant = 8
4884
+ self.viewChangedAccount.isHidden = true
4885
+ }
4886
+ else {
4887
+ self.viewAddNewGrailPayAccount.isHidden = true
4888
+
4889
+ self.viewNewBankAccount.isHidden = false
4890
+ self.viewSingleSavedAccount.isHidden = true
4891
+ self.viewBankFields.isHidden = true
4892
+ self.viewBtnShowSavedCards.isHidden = true
4893
+ self.viewBtnShowSavedCardHeight.constant = 0
4894
+ self.viewBtnShowSavedCardTopCon.constant = 0
4895
+ self.OTPView.isHidden = true
4896
+ self.emailView.isHidden = true
4897
+ self.viewCardFields.isHidden = true
4898
+ self.viewSingleSavedCard.isHidden = true
4899
+ self.viewChangeCard.isHidden = true
4900
+ self.viewUpdateCard.isHidden = true
4901
+ self.viewAddNewCard.isHidden = true
4902
+ self.btnNext.isHidden = true
4903
+ self.btnNextHeight.constant = 0
4904
+ self.btnNextTopCon.constant = 8
4905
+ self.viewChangedAccount.isHidden = true
4906
+ }
4907
+
4630
4908
  }
4631
-
4909
+
4632
4910
  @objc func btnSelectAccountTapped(_ sender: UIButton) {
4633
4911
  let index = sender.tag
4634
4912
  // Store the index of the previously selected card
4635
4913
  let previousSelectedIndex = selectedBankIndex
4636
-
4914
+
4637
4915
  // Toggle the selection state: if the same card is selected, deselect it; otherwise, select the new one
4638
4916
  selectedBankIndex = (selectedBankIndex == index) ? nil : index
4639
-
4917
+
4640
4918
  // Update the single card view
4641
4919
  if let selectedIndex = selectedBankIndex {
4642
4920
  updateSingleBankView(for: selectedIndex)
@@ -4645,35 +4923,35 @@ extension PaymentInfoVC: UITableViewDelegate, UITableViewDataSource {
4645
4923
  } else {
4646
4924
  viewSingleSavedAccount.isHidden = true
4647
4925
  }
4648
-
4926
+
4649
4927
  // Reload the previously selected row to update its state
4650
4928
  if let previousIndex = previousSelectedIndex {
4651
4929
  tblViewSavedBankAccounts.reloadRows(at: [IndexPath(row: previousIndex, section: 0)], with: .automatic)
4652
4930
  }
4653
-
4931
+
4654
4932
  // Reload the newly selected row to update its state
4655
4933
  tblViewSavedBankAccounts.reloadRows(at: [IndexPath(row: index, section: 0)], with: .automatic)
4656
4934
  }
4657
-
4935
+
4658
4936
  private func updateSingleBankView(for index: Int) {
4659
4937
  let selectedCard = bankAccounts[index]
4660
4938
  lblAccountNumberSingleAccountView.text = "****\(selectedCard.account_number_last_4 ?? "")"
4661
4939
  lblAccountTypeSingleAccountView.text = "Type: \(selectedCard.account_type ?? "")"
4662
-
4940
+
4663
4941
  if isSelectForPayBank {
4664
4942
  viewTermAndConditionsSingleAccountView.isHidden = false
4665
4943
  btnPayNowSingleAccountView.isHidden = false
4666
- viewSingleAccountViewHeight.constant = 170
4944
+ // viewSingleAccountViewHeight.constant = 170
4667
4945
  } else {
4668
4946
  viewTermAndConditionsSingleAccountView.isHidden = true
4669
4947
  btnPayNowSingleAccountView.isHidden = true
4670
- viewSingleAccountViewHeight.constant = 68
4948
+ // viewSingleAccountViewHeight.constant = 68
4671
4949
  btnNext.isHidden = true
4672
4950
  btnNextHeight.constant = 0
4673
4951
  btnNextTopCon.constant = 0
4674
4952
  }
4675
4953
  }
4676
-
4954
+
4677
4955
  @objc func btnThreeDotBankTapped(_ sender: UIButton) {
4678
4956
  let index = sender.tag
4679
4957
  guard let cell = tblViewSavedBankAccounts.cellForRow(at: IndexPath(row: index, section: 0)) as? SavedAccountTVC else {
@@ -4683,13 +4961,13 @@ extension PaymentInfoVC: UITableViewDelegate, UITableViewDataSource {
4683
4961
  cell.isRemoveUpdateViewVisible.toggle()
4684
4962
  cell.viewBtnRemove.isHidden = !cell.isRemoveUpdateViewVisible
4685
4963
  }
4686
-
4964
+
4687
4965
  @objc func btnRemoveBankAccountTapped(_ sender: UIButton) {
4688
4966
  let index = sender.tag
4689
-
4967
+
4690
4968
  let selectedAccount = bankAccounts[index]
4691
4969
  selectedBankID = selectedAccount.account_id
4692
-
4970
+
4693
4971
  let alertController = UIAlertController(
4694
4972
  title: "Confirm Deletion",
4695
4973
  message: "Are you sure you want to delete this account?",
@@ -4705,12 +4983,12 @@ extension PaymentInfoVC: UITableViewDelegate, UITableViewDataSource {
4705
4983
  alertController.addAction(yesAction)
4706
4984
  present(alertController, animated: true, completion: nil)
4707
4985
  }
4708
-
4986
+
4709
4987
  }
4710
4988
 
4711
4989
  // MARK: - Text Field Delegate Methods
4712
4990
  extension PaymentInfoVC: UITextFieldDelegate {
4713
-
4991
+
4714
4992
  func textFieldShouldReturn(_ textField: UITextField) -> Bool {
4715
4993
  if textField == txtFieldEmail {
4716
4994
  // Check if the email field is empty
@@ -4735,7 +5013,7 @@ extension PaymentInfoVC: UITextFieldDelegate {
4735
5013
  textField.resignFirstResponder()
4736
5014
  return true
4737
5015
  }
4738
-
5016
+
4739
5017
  func textFieldDidChangeSelection(_ textField: UITextField) {
4740
5018
  guard let otpCode = textField.text, otpCode.count >= 6, textField.textContentType == .oneTimeCode else { return }
4741
5019
  txtFieldOTPText1.text = String(otpCode[otpCode.index(otpCode.startIndex, offsetBy: 0)])
@@ -4746,7 +5024,7 @@ extension PaymentInfoVC: UITextFieldDelegate {
4746
5024
  txtFieldOTPText6.text = String(otpCode[otpCode.index(otpCode.startIndex, offsetBy: 5)])
4747
5025
  txtFieldOTPText6.becomeFirstResponder()
4748
5026
  }
4749
-
5027
+
4750
5028
  func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
4751
5029
  // Handle card number input for both old and new card fields
4752
5030
  if textField == cardNumberTextField.textField || textField == txtFieldCardNumberNewCardView {
@@ -4755,20 +5033,20 @@ extension PaymentInfoVC: UITextFieldDelegate {
4755
5033
  textField.text = formatCardNumber(modifiedText)
4756
5034
  return false
4757
5035
  }
4758
-
5036
+
4759
5037
  let text = (textField.text ?? "") + string
4760
5038
  if text.filter({ $0.isNumber }).count > 16 {
4761
5039
  return false
4762
5040
  }
4763
-
5041
+
4764
5042
  textField.text = formatCardNumber(text)
4765
5043
  if let lastCharacter = text.last, lastCharacter == " " {
4766
5044
  textField.text = String(text.dropLast())
4767
5045
  }
4768
-
5046
+
4769
5047
  return false
4770
5048
  }
4771
-
5049
+
4772
5050
  // Handle card CVV input for both old and new card fields
4773
5051
  else if textField == cardCvvTextField.textField || textField == txtFieldCVVNewCardView || textField == txtFieldCVVUpdateCardView {
4774
5052
  let maxLength = 3
@@ -4776,7 +5054,7 @@ extension PaymentInfoVC: UITextFieldDelegate {
4776
5054
  let newString = currentString.replacingCharacters(in: range, with: string)
4777
5055
  return newString.count <= maxLength
4778
5056
  }
4779
-
5057
+
4780
5058
  // Handle card expiry date input for both old and new card fields
4781
5059
  else if textField == cardExpiryTextField.textField || textField == txtFieldExpiryDateNewCardView || textField == txtFieldExpireDateUpdateCardView {
4782
5060
  if range.length > 0 {
@@ -4788,24 +5066,24 @@ extension PaymentInfoVC: UITextFieldDelegate {
4788
5066
  if range.location > 6 {
4789
5067
  return false
4790
5068
  }
4791
-
5069
+
4792
5070
  var originalText = textField.text ?? ""
4793
5071
  let replacementText = string.replacingOccurrences(of: " ", with: "")
4794
-
5072
+
4795
5073
  // Ensure only digits are allowed
4796
5074
  if !CharacterSet.decimalDigits.isSuperset(of: CharacterSet(charactersIn: replacementText)) {
4797
5075
  return false
4798
5076
  }
4799
-
5077
+
4800
5078
  // Insert "/" after MM
4801
5079
  if range.location == 2 {
4802
5080
  originalText.append("/")
4803
5081
  }
4804
-
5082
+
4805
5083
  textField.text = originalText + replacementText
4806
5084
  return false
4807
5085
  }
4808
-
5086
+
4809
5087
  // Handle OTP input fields
4810
5088
  else if [txtFieldOTPText1, txtFieldOTPText2, txtFieldOTPText3, txtFieldOTPText4, txtFieldOTPText5, txtFieldOTPText6].contains(textField) {
4811
5089
  if string.count == 1 {
@@ -4855,10 +5133,29 @@ extension PaymentInfoVC: UITextFieldDelegate {
4855
5133
  }
4856
5134
  return textField.text?.count == 0
4857
5135
  }
4858
-
5136
+
5137
+ else if textField == txtFieldRoutingNumber {
5138
+ let allowedCharacters = CharacterSet.decimalDigits
5139
+ let characterSet = CharacterSet(charactersIn: string)
5140
+
5141
+ let currentText = textField.text ?? ""
5142
+ let newLength = (currentText as NSString).replacingCharacters(in: range, with: string).count
5143
+
5144
+ return allowedCharacters.isSuperset(of: characterSet) && newLength <= 9
5145
+ }
5146
+ else if textField == txtFieldRoutingNumberNewAccountView {
5147
+ let allowedCharacters = CharacterSet.decimalDigits
5148
+ let characterSet = CharacterSet(charactersIn: string)
5149
+
5150
+ let currentText = textField.text ?? ""
5151
+ let newLength = (currentText as NSString).replacingCharacters(in: range, with: string).count
5152
+
5153
+ return allowedCharacters.isSuperset(of: characterSet) && newLength <= 9
5154
+ }
5155
+
4859
5156
  return true
4860
5157
  }
4861
-
5158
+
4862
5159
  // Helper method to format the card number with spaces
4863
5160
  func formatCardNumber(_ input: String) -> String {
4864
5161
  var formattedString = ""
@@ -4872,14 +5169,14 @@ extension PaymentInfoVC: UITextFieldDelegate {
4872
5169
  }
4873
5170
  return formattedString
4874
5171
  }
4875
-
5172
+
4876
5173
  // Update the view colors based on text presence in OTP fields
4877
5174
  private func updateViewColors() {
4878
5175
  if let primaryBtnBackgroundColor = UserStoreSingleton.shared.primary_btn_bg_col,
4879
5176
  let primaryUIColor = UIColor(hex: primaryBtnBackgroundColor),
4880
5177
  let secondaryFontColor = UserStoreSingleton.shared.secondary_font_col,
4881
5178
  let secondaryUIColor = UIColor(hex: secondaryFontColor) {
4882
-
5179
+
4883
5180
  viewTextOTP1.layer.borderWidth = 1.0
4884
5181
  viewTextOTP1.layer.borderColor = txtFieldOTPText1.text?.isEmpty == false ? primaryUIColor.cgColor : secondaryUIColor.cgColor
4885
5182
  viewTextOTP2.layer.borderWidth = 1.0
@@ -4908,7 +5205,7 @@ extension PaymentInfoVC: UITextFieldDelegate {
4908
5205
  viewTextOTP6.layer.borderColor = (txtFieldOTPText6.text?.isEmpty == false ? UIColor.systemBlue : UIColor.systemGray).cgColor
4909
5206
  }
4910
5207
  }
4911
-
5208
+
4912
5209
  @objc func textFieldDidChange(_ textField: UITextField) {
4913
5210
  guard let otpCode = textField.text, otpCode.count >= 6, textField.textContentType == .oneTimeCode else { return }
4914
5211
  // Split the OTP into each text field
@@ -4921,7 +5218,7 @@ extension PaymentInfoVC: UITextFieldDelegate {
4921
5218
  // Automatically move to the last text field
4922
5219
  txtFieldOTPText6.becomeFirstResponder()
4923
5220
  }
4924
-
5221
+
4925
5222
  func getCombinedOTP() -> String {
4926
5223
  let otp1 = txtFieldOTPText1.text ?? ""
4927
5224
  let otp2 = txtFieldOTPText2.text ?? ""
@@ -4929,11 +5226,11 @@ extension PaymentInfoVC: UITextFieldDelegate {
4929
5226
  let otp4 = txtFieldOTPText4.text ?? ""
4930
5227
  let otp5 = txtFieldOTPText5.text ?? ""
4931
5228
  let otp6 = txtFieldOTPText6.text ?? ""
4932
-
5229
+
4933
5230
  return otp1 + otp2 + otp3 + otp4 + otp5 + otp6
4934
5231
  }
4935
-
4936
- // UITextFieldDelegate methods
5232
+
5233
+ //MARK: - UITextFieldDelegate methods
4937
5234
  func textFieldDidBeginEditing(_ textField: UITextField) {
4938
5235
  if textField == cardNumberTextField.textField {
4939
5236
  if let primaryBtnFontColor = UserStoreSingleton.shared.primary_btn_bg_col,
@@ -5073,6 +5370,14 @@ extension PaymentInfoVC: UITextFieldDelegate {
5073
5370
  viewtxtFieldAccountNumber.layer.borderColor = UIColor._1757D9.withAlphaComponent(1).cgColor
5074
5371
  }
5075
5372
  }
5373
+ else if textField == txtFieldConfirmBankAccount {
5374
+ if let primaryBtnFontColor = UserStoreSingleton.shared.primary_btn_bg_col,
5375
+ let uiColor = UIColor(hex: primaryBtnFontColor) {
5376
+ viewTextFieldConfirmAccount.layer.borderColor = uiColor.cgColor
5377
+ } else {
5378
+ viewTextFieldConfirmAccount.layer.borderColor = UIColor._1757D9.withAlphaComponent(1).cgColor
5379
+ }
5380
+ }
5076
5381
  //New Bank Account View
5077
5382
  else if textField == txtFieldAccountNameNewAccountView {
5078
5383
  if let primaryBtnFontColor = UserStoreSingleton.shared.primary_btn_bg_col,
@@ -5106,8 +5411,16 @@ extension PaymentInfoVC: UITextFieldDelegate {
5106
5411
  viewtxtFieldAccountNumberNewAccountView.layer.borderColor = UIColor._1757D9.withAlphaComponent(1).cgColor
5107
5412
  }
5108
5413
  }
5414
+ else if textField == txtFieldConfirmAccountNewAccountView {
5415
+ if let primaryBtnFontColor = UserStoreSingleton.shared.primary_btn_bg_col,
5416
+ let uiColor = UIColor(hex: primaryBtnFontColor) {
5417
+ viewTxtFieldConfirmNewAccountView.layer.borderColor = uiColor.cgColor
5418
+ } else {
5419
+ viewTxtFieldConfirmNewAccountView.layer.borderColor = UIColor._1757D9.withAlphaComponent(1).cgColor
5420
+ }
5421
+ }
5109
5422
  }
5110
-
5423
+
5111
5424
  func textFieldDidEndEditing(_ textField: UITextField) {
5112
5425
  if textField == cardNumberTextField.textField {
5113
5426
  if let bodyBackgroundColor = UserStoreSingleton.shared.secondary_font_col,
@@ -5248,6 +5561,14 @@ extension PaymentInfoVC: UITextFieldDelegate {
5248
5561
  viewtxtFieldAccountNumber.layer.borderColor = UIColor.systemGray.cgColor
5249
5562
  }
5250
5563
  }
5564
+ else if textField == txtFieldConfirmBankAccount {
5565
+ if let bodyBackgroundColor = UserStoreSingleton.shared.secondary_font_col,
5566
+ let borderColor = UIColor(hex: bodyBackgroundColor) {
5567
+ viewTextFieldConfirmAccount.layer.borderColor = borderColor.cgColor
5568
+ } else {
5569
+ viewTextFieldConfirmAccount.layer.borderColor = UIColor.systemGray.cgColor
5570
+ }
5571
+ }
5251
5572
  //New Bank Account View
5252
5573
  else if textField == txtFieldAccountNameNewAccountView {
5253
5574
  if let bodyBackgroundColor = UserStoreSingleton.shared.secondary_font_col,
@@ -5281,36 +5602,43 @@ extension PaymentInfoVC: UITextFieldDelegate {
5281
5602
  viewtxtFieldAccountNumberNewAccountView.layer.borderColor = UIColor.systemGray.cgColor
5282
5603
  }
5283
5604
  }
5605
+ else if textField == txtFieldConfirmAccountNewAccountView {
5606
+ if let bodyBackgroundColor = UserStoreSingleton.shared.secondary_font_col,
5607
+ let borderColor = UIColor(hex: bodyBackgroundColor) {
5608
+ viewTxtFieldConfirmNewAccountView.layer.borderColor = borderColor.cgColor
5609
+ } else {
5610
+ viewTxtFieldConfirmNewAccountView.layer.borderColor = UIColor.systemGray.cgColor
5611
+ }
5612
+ }
5284
5613
  }
5285
-
5614
+
5286
5615
  }
5287
5616
 
5288
5617
  //MARK: - BlinkCard
5289
-
5290
5618
  //extension PaymentInfoVC: MBCBlinkCardOverlayViewControllerDelegate {
5291
5619
  // func blinkCardOverlayViewControllerDidFinishScanning(_ blinkCardOverlayViewController: MBCBlinkCardOverlayViewController, state: MBCRecognizerResultState) {
5292
5620
  // /** This is done on background thread */
5293
5621
  // blinkCardOverlayViewController.recognizerRunnerViewController?.pauseScanning()
5294
- //
5622
+ //
5295
5623
  // var message: String = ""
5296
5624
  // var title: String = ""
5297
- //
5625
+ //
5298
5626
  // if blinkCardRecognizer.result.resultState == .valid {
5299
5627
  // title = "Payment card"
5300
- //
5628
+ //
5301
5629
  // let fullDocumentImage: UIImage! = blinkCardRecognizer.result.firstSideFullDocumentImage?.image
5302
5630
  // print("Got payment card image with width: \(fullDocumentImage.size.width), height: \(fullDocumentImage.size.height)")
5303
- //
5631
+ //
5304
5632
  // // Save the string representation of the code
5305
5633
  // message = blinkCardRecognizer.result.description
5306
5634
  // }
5307
- //
5635
+ //
5308
5636
  // /** Needs to be called on main thread beacuse everything prior is on background thread */
5309
5637
  // DispatchQueue.main.async {
5310
5638
  // // present the alert view with scanned results
5311
- //
5639
+ //
5312
5640
  // let alertController: UIAlertController = UIAlertController.init(title: title, message: message, preferredStyle: .alert)
5313
- //
5641
+ //
5314
5642
  // let okAction: UIAlertAction = UIAlertAction.init(title: "OK", style: .default,
5315
5643
  // handler: { (action) -> Void in
5316
5644
  // self.dismiss(animated: true, completion: nil)
@@ -5319,20 +5647,20 @@ extension PaymentInfoVC: UITextFieldDelegate {
5319
5647
  // blinkCardOverlayViewController.present(alertController, animated: true, completion: nil)
5320
5648
  // }
5321
5649
  // }
5322
- //
5650
+ //
5323
5651
  // func blinkCardOverlayViewControllerDidTapClose(_ blinkCardOverlayViewController: MBCBlinkCardOverlayViewController) {
5324
5652
  // self.dismiss(animated: true, completion: nil)
5325
5653
  // }
5326
- //
5654
+ //
5327
5655
  // func blinkCardOverlayViewControllerDidFinishEditing(_ blinkCardOverlayViewController: MBCBlinkCardOverlayViewController, editResult: MBCBlinkCardEditResult) {
5328
5656
  // blinkCardOverlayViewController.recognizerRunnerViewController?.pauseScanning()
5329
- //
5657
+ //
5330
5658
  // /** Needs to be called on main thread beacuse everything prior is on background thread */
5331
5659
  // DispatchQueue.main.async {
5332
5660
  // // present the alert view with scanned results
5333
- //
5661
+ //
5334
5662
  // let alertController: UIAlertController = UIAlertController.init(title: "BlinkCard Edit Results", message: editResult.description, preferredStyle: .alert)
5335
- //
5663
+ //
5336
5664
  // let okAction: UIAlertAction = UIAlertAction.init(title: "OK", style: .default,
5337
5665
  // handler: { (action) -> Void in
5338
5666
  // self.dismiss(animated: true, completion: nil)
@@ -5341,7 +5669,7 @@ extension PaymentInfoVC: UITextFieldDelegate {
5341
5669
  // blinkCardOverlayViewController.blinkCardEditViewController?.present(alertController, animated: true, completion: nil)
5342
5670
  // }
5343
5671
  // }
5344
- //
5672
+ //
5345
5673
  //}
5346
5674
 
5347
5675
  //extension PaymentInfoVC: CustomOverlayDelegate {
@@ -5352,5 +5680,5 @@ extension PaymentInfoVC: UITextFieldDelegate {
5352
5680
  // cardCvvTextField.text = cvv
5353
5681
  // cardNameTextField.text = nameOnCard
5354
5682
  // }
5355
- //
5683
+ //
5356
5684
  //}