@jimrising/easymerchantsdk-react-native 2.2.2 → 2.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/README.md +1 -1
  2. package/ios/Pods/UserDefaults/UserStoreSingleton.swift +304 -0
  3. package/ios/Pods/ViewControllers/AdditionalInfoVC.swift +2636 -0
  4. package/ios/Pods/ViewControllers/BaseVC.swift +141 -0
  5. package/ios/Pods/ViewControllers/BillingInfoVC/BillingInfoVC.swift +3347 -0
  6. package/ios/Pods/ViewControllers/BillingInfoVC/Cells/CityListTVC.swift +46 -0
  7. package/ios/Pods/ViewControllers/BillingInfoVC/Cells/CountryListTVC.swift +47 -0
  8. package/ios/Pods/ViewControllers/BillingInfoVC/Cells/StateListTVC.swift +46 -0
  9. package/ios/Pods/ViewControllers/CountryListVC.swift +435 -0
  10. package/ios/Pods/ViewControllers/CustomOverlay.swift +199 -0
  11. package/ios/Pods/ViewControllers/EmailVerificationVC.swift +307 -0
  12. package/ios/Pods/ViewControllers/GrailPayVC.swift +244 -0
  13. package/ios/Pods/ViewControllers/OTPVerificationVC.swift +2066 -0
  14. package/ios/Pods/ViewControllers/PaymentDoneVC.swift +272 -0
  15. package/ios/Pods/ViewControllers/PaymentErrorVC.swift +85 -0
  16. package/ios/Pods/ViewControllers/PaymentInformation/AccountTypeTVC.swift +41 -0
  17. package/ios/Pods/ViewControllers/PaymentInformation/PaymentInfoVC.swift +11025 -0
  18. package/ios/Pods/ViewControllers/PaymentInformation/PaymentInformationCVC.swift +35 -0
  19. package/ios/Pods/ViewControllers/PaymentInformation/RecurringTVC.swift +40 -0
  20. package/ios/Pods/ViewControllers/PaymentInformation/SavedAccountsTVC/SavedAccountTVC.swift +81 -0
  21. package/ios/Pods/ViewControllers/PaymentInformation/SavedAccountsTVC/SavedAccountTVC.xib +163 -0
  22. package/ios/Pods/ViewControllers/PaymentInformation/SavedCardsTVC/SavedCardsTVC.swift +81 -0
  23. package/ios/Pods/ViewControllers/PaymentInformation/SavedCardsTVC/SavedCardsTVC.xib +188 -0
  24. package/ios/Pods/ViewControllers/PaymentStatusWebViewVC.swift +167 -0
  25. package/ios/Pods/ViewControllers/TermAndConditionsVC.swift +63 -0
  26. package/ios/Pods/ViewControllers/ThreeDSecurePaymentDoneVC.swift +629 -0
  27. package/ios/easymerchantsdk.podspec +1 -1
  28. package/package.json +1 -1
@@ -0,0 +1,629 @@
1
+ //
2
+ // ThreeDSecurePaymentDoneVC.swift
3
+ // EasyPay
4
+ //
5
+ // Created by Mony's Mac on 20/05/25.
6
+ //
7
+
8
+ import UIKit
9
+
10
+ class ThreeDSecurePaymentDoneVC: BaseVC {
11
+
12
+ @IBOutlet weak var imgViewPaymentDone: UIImageView!
13
+ @IBOutlet weak var imgViewLoading: UIImageView!
14
+ @IBOutlet weak var viewMain: UIView!
15
+ @IBOutlet weak var lblCompleteAuthentication: UILabel!
16
+ @IBOutlet weak var btnDone: UIButton!
17
+ // @IBOutlet weak var lblPaymentLink: UILabel!
18
+ @IBOutlet weak var lblBillingInfoData: UILabel!
19
+
20
+ @IBOutlet weak var viewPaymentDetails: UIStackView!
21
+ @IBOutlet weak var lblDateHeading: UILabel!
22
+ @IBOutlet weak var lblTransactionHeading: UILabel!
23
+ @IBOutlet weak var lblSubscriptionHeading: UILabel!
24
+ @IBOutlet weak var lblChargeIdHeading: UILabel!
25
+ @IBOutlet weak var lblReferenceIdHeading: UILabel!
26
+ @IBOutlet weak var lblPaymentMethodHeading: UILabel!
27
+ @IBOutlet weak var lblTotalHeading: UILabel!
28
+ @IBOutlet weak var lblStatusHeading: UILabel!
29
+
30
+ @IBOutlet weak var lblDate: UILabel!
31
+ @IBOutlet weak var lblTransactionId: UILabel!
32
+ @IBOutlet weak var lblSubscriptionId: UILabel!
33
+ @IBOutlet weak var lblChargeId: UILabel!
34
+ @IBOutlet weak var lblTotal: UILabel!
35
+ @IBOutlet weak var lblRefferenceId: UILabel!
36
+ @IBOutlet weak var lblPaymentMethodValue: UILabel!
37
+ @IBOutlet weak var lblStatus: UILabel!
38
+
39
+ @IBOutlet weak var btnContinue: UIButton!
40
+
41
+ var easyPayDelegate: EasyPayViewControllerDelegate?
42
+ var redirectURL: String?
43
+ var chargeData: [String: Any] = [:]
44
+
45
+ var billingInfo: [String: Any]?
46
+ var additionalInfo: [String: Any]?
47
+
48
+ var threeDSecureStatusResponse: [String: Any]?
49
+
50
+ // Rename for clarity, this will be for the periodic API check
51
+ var apiStatusCheckTimer: Timer?
52
+ // New property for the 1-minute countdown
53
+ var oneMinuteCountdownTimer: DispatchSourceTimer?
54
+ var countdownRemaining: Int = 150 // 120 seconds
55
+
56
+ var additionalInfoData: [FieldItem]?
57
+ var billingInfoData: [FieldItem]?
58
+ var visibility: FieldsVisibility?
59
+
60
+ // Dispatch group to wait for initial API call
61
+ let initialAPIGroup = DispatchGroup()
62
+
63
+ // var amount: Int?
64
+ var amount: Double?
65
+
66
+ var cardApiParams: [String: Any]?
67
+
68
+ var request: Request!
69
+
70
+ var didTapContinue = false
71
+
72
+ override func viewDidLoad() {
73
+ super.viewDidLoad()
74
+ uiFinishingTouchElements()
75
+
76
+ // setupTapOnLabel()
77
+
78
+ // Initially hide done image and show loading image
79
+ imgViewPaymentDone.isHidden = true
80
+ imgViewLoading.isHidden = false
81
+ lblCompleteAuthentication.text = "Completing Authentication..." // Initial text
82
+ btnDone.isHidden = true
83
+
84
+ startRotatingImage()
85
+ startOneMinuteCountdownAndAPICheck()
86
+
87
+ NotificationCenter.default.addObserver(self, selector: #selector(appDidBecomeActive), name: UIApplication.didBecomeActiveNotification, object: nil)
88
+ }
89
+
90
+ deinit {
91
+ NotificationCenter.default.removeObserver(self)
92
+ }
93
+
94
+ @objc private func appDidBecomeActive() {
95
+ if !imgViewLoading.isHidden && imgViewLoading.layer.animation(forKey: "rotationAnimation") == nil {
96
+ startRotatingImage()
97
+ }
98
+ }
99
+
100
+ override func viewWillAppear(_ animated: Bool) {
101
+ super.viewWillAppear(animated)
102
+ uiFinishingTouchElements()
103
+
104
+ // Restart rotating animation if missing
105
+ if !imgViewLoading.isHidden && imgViewLoading.layer.animation(forKey: "rotationAnimation") == nil {
106
+ startRotatingImage()
107
+ }
108
+
109
+ if let chargeId = (threeDSecureStatusResponse?["data"] as? [String: Any])?["charge_id"] as? String,
110
+ !chargeId.isEmpty {
111
+ // Charge already found - do nothing
112
+ return
113
+ }
114
+
115
+ if countdownRemaining > 0 {
116
+ imgViewLoading.isHidden = false
117
+ imgViewPaymentDone.isHidden = true
118
+ // lblCompleteAuthentication.text = "Click the link below to complete 3DS Authentication.!"
119
+ if !didTapContinue {
120
+ lblCompleteAuthentication.text = "Tap Continue to proceed with 3D Secure authentication."
121
+ }
122
+ btnDone.isHidden = true
123
+
124
+ if apiStatusCheckTimer == nil {
125
+ apiStatusCheckTimer = Timer.scheduledTimer(withTimeInterval: 5.0, repeats: true) { [weak self] _ in
126
+ guard let self = self else { return }
127
+ if self.countdownRemaining > 0 {
128
+ self.checkThreeDSecureStatus(completion: nil)
129
+ }
130
+ }
131
+ }
132
+
133
+ } else {
134
+ imgViewLoading.layer.removeAllAnimations()
135
+ imgViewLoading.isHidden = true
136
+ imgViewPaymentDone.isHidden = false
137
+ imgViewPaymentDone.image = UIImage(systemName: "exclamationmark.circle.fill")
138
+ imgViewPaymentDone.tintColor = .systemRed
139
+ // lblCompleteAuthentication.text = "Click the link below to complete 3DS Authentication.!"
140
+ if !didTapContinue {
141
+ lblCompleteAuthentication.text = "Tap Continue to proceed with 3D Secure authentication."
142
+ }
143
+ btnDone.isHidden = false
144
+ // lblPaymentLink.isHidden = false
145
+ viewPaymentDetails.isHidden = false
146
+
147
+ apiStatusCheckTimer?.invalidate()
148
+ apiStatusCheckTimer = nil
149
+ }
150
+ }
151
+
152
+ // override func viewWillAppear(_ animated: Bool) {
153
+ // super.viewWillAppear(animated)
154
+ // uiFinishingTouchElements()
155
+ //
156
+ // // Restart rotating animation if missing
157
+ // if !imgViewLoading.isHidden && imgViewLoading.layer.animation(forKey: "rotationAnimation") == nil {
158
+ // startRotatingImage()
159
+ // }
160
+ //
161
+ // // Check if chargeId already exists (success)
162
+ // if let chargeId = (threeDSecureStatusResponse?["data"] as? [String: Any])?["charge_id"] as? String,
163
+ // !chargeId.isEmpty {
164
+ // // ✅ Charge already found - no further API checks needed
165
+ // return
166
+ // }
167
+ //
168
+ // // If user came back after tapping Continue
169
+ // if didTapContinue {
170
+ // lblCompleteAuthentication.text = "Waiting for 3DS Authentication..."
171
+ // btnContinue.isHidden = true
172
+ // imgViewLoading.isHidden = false
173
+ // imgViewPaymentDone.isHidden = true
174
+ // startRotatingImage()
175
+ // } else {
176
+ // // Initial load or user hasn’t tapped Continue yet
177
+ // lblCompleteAuthentication.text = "Tap Continue to proceed with 3D Secure authentication."
178
+ // btnContinue.isHidden = false
179
+ // }
180
+ //
181
+ // btnDone.isHidden = true
182
+ // viewPaymentDetails.isHidden = true
183
+ //
184
+ // if countdownRemaining > 0 {
185
+ // if apiStatusCheckTimer == nil {
186
+ // apiStatusCheckTimer = Timer.scheduledTimer(withTimeInterval: 5.0, repeats: true) { [weak self] _ in
187
+ // guard let self = self else { return }
188
+ // if self.countdownRemaining > 0 {
189
+ // self.checkThreeDSecureStatus(completion: nil)
190
+ // }
191
+ // }
192
+ // }
193
+ // } else {
194
+ // // Countdown expired, stop API check
195
+ // apiStatusCheckTimer?.invalidate()
196
+ // apiStatusCheckTimer = nil
197
+ //
198
+ // imgViewLoading.layer.removeAllAnimations()
199
+ // imgViewLoading.isHidden = true
200
+ // imgViewPaymentDone.isHidden = false
201
+ // imgViewPaymentDone.image = UIImage(systemName: "exclamationmark.circle.fill")
202
+ // imgViewPaymentDone.tintColor = .systemRed
203
+ //
204
+ // lblCompleteAuthentication.text = "Tap Continue to proceed with 3D Secure authentication."
205
+ // btnDone.isHidden = false
206
+ // viewPaymentDetails.isHidden = false
207
+ // }
208
+ // }
209
+
210
+ override func viewWillDisappear(_ animated: Bool) {
211
+ super.viewWillDisappear(animated)
212
+ apiStatusCheckTimer?.invalidate()
213
+ apiStatusCheckTimer = nil
214
+ oneMinuteCountdownTimer?.cancel() // Cancel the dispatch source timer
215
+ oneMinuteCountdownTimer = nil
216
+ }
217
+
218
+ private func showHourglassGIF() {
219
+ guard let bundlePath = Bundle.easyPayBundle.path(forResource: "hourglass", ofType: "gif"),
220
+ let gifData = try? Data(contentsOf: URL(fileURLWithPath: bundlePath)),
221
+ let source = CGImageSourceCreateWithData(gifData as CFData, nil) else {
222
+ print("⚠️ Could not load GIF from bundle")
223
+ return
224
+ }
225
+
226
+ var images: [UIImage] = []
227
+ var duration: Double = 0
228
+
229
+ let frameCount = CGImageSourceGetCount(source)
230
+ for i in 0..<frameCount {
231
+ if let cgImage = CGImageSourceCreateImageAtIndex(source, i, nil) {
232
+ let frameDuration = getFrameDuration(from: source, index: i)
233
+ duration += frameDuration
234
+ images.append(UIImage(cgImage: cgImage))
235
+ }
236
+ }
237
+
238
+ if !images.isEmpty {
239
+ let animatedImage = UIImage.animatedImage(with: images, duration: duration)
240
+ imgViewLoading.image = animatedImage
241
+ imgViewLoading.contentMode = .scaleToFill
242
+ }
243
+ }
244
+
245
+ private func getFrameDuration(from source: CGImageSource, index: Int) -> Double {
246
+ let defaultFrameDuration = 0.1
247
+
248
+ guard let properties = CGImageSourceCopyPropertiesAtIndex(source, index, nil) as? [CFString: Any],
249
+ let gifProperties = properties[kCGImagePropertyGIFDictionary] as? [CFString: Any] else {
250
+ return defaultFrameDuration
251
+ }
252
+
253
+ if let unclampedDelay = gifProperties[kCGImagePropertyGIFUnclampedDelayTime] as? Double, unclampedDelay > 0 {
254
+ return unclampedDelay
255
+ }
256
+
257
+ if let delay = gifProperties[kCGImagePropertyGIFDelayTime] as? Double, delay > 0 {
258
+ return delay
259
+ }
260
+
261
+ return defaultFrameDuration
262
+ }
263
+
264
+ func startRotatingImage() {
265
+ showHourglassGIF()
266
+ }
267
+
268
+ @IBAction func actionBtnContinue(_ sender: UIButton) {
269
+ guard let urlStr = redirectURL, !urlStr.isEmpty else {
270
+ print("Redirect URL is nil or empty")
271
+ return
272
+ }
273
+
274
+ didTapContinue = true
275
+ lblCompleteAuthentication.text = "Wating for 3DS Authentication..."
276
+ btnContinue.isHidden = true
277
+
278
+ let vc = easymerchantsdk.instantiateViewController(withIdentifier: "PaymentStatusWebViewVC") as! PaymentStatusWebViewVC
279
+ vc.urlString = urlStr // Pass the URL to WebView VC
280
+ self.navigationController?.pushViewController(vc, animated: true)
281
+ }
282
+
283
+ private func startOneMinuteCountdownAndAPICheck() {
284
+ startRotatingImage()
285
+ imgViewPaymentDone.isHidden = true
286
+ imgViewLoading.isHidden = false
287
+ // lblCompleteAuthentication.text = "Click the link below to complete 3DS Authentication.!"
288
+ if !didTapContinue {
289
+ lblCompleteAuthentication.text = "Tap Continue to proceed with 3D Secure authentication."
290
+ }
291
+ btnDone.isHidden = true
292
+
293
+ apiStatusCheckTimer?.invalidate()
294
+ apiStatusCheckTimer = nil
295
+
296
+ initialAPIGroup.enter()
297
+ checkThreeDSecureStatus { [weak self] in
298
+ self?.initialAPIGroup.leave()
299
+ }
300
+
301
+ countdownRemaining = 150
302
+
303
+ oneMinuteCountdownTimer = DispatchSource.makeTimerSource(queue: .main)
304
+ oneMinuteCountdownTimer?.schedule(deadline: .now(), repeating: .seconds(1))
305
+ oneMinuteCountdownTimer?.setEventHandler { [weak self] in
306
+ guard let self = self else { return }
307
+
308
+ self.countdownRemaining -= 1
309
+ if self.countdownRemaining <= 0 {
310
+ self.oneMinuteCountdownTimer?.cancel()
311
+ self.oneMinuteCountdownTimer = nil
312
+
313
+ DispatchQueue.main.async {
314
+ self.imgViewLoading.layer.removeAllAnimations()
315
+ self.imgViewLoading.isHidden = true
316
+ self.imgViewPaymentDone.isHidden = false
317
+ // self.lblCompleteAuthentication.text = "Click the link below to complete 3DS Authentication.!"
318
+ if !self.didTapContinue {
319
+ self.lblCompleteAuthentication.text = "Tap Continue to proceed with 3D Secure authentication."
320
+ }
321
+ self.btnDone.isHidden = false
322
+ // self.lblPaymentLink.isHidden = false
323
+ self.viewPaymentDetails.isHidden = false
324
+
325
+ if let json = self.threeDSecureStatusResponse,
326
+ let data = json["data"] as? [String: Any],
327
+ let chargeId = data["charge_id"],
328
+ chargeId is NSNull || (chargeId as? String ?? "").isEmpty {
329
+ self.imgViewPaymentDone.image = UIImage(systemName: "exclamationmark.circle.fill")
330
+ self.imgViewPaymentDone.tintColor = .systemRed
331
+
332
+ let refToken = data["ref_token"] as? String ?? "N/A"
333
+ let chargeId = data["charge_id"] as? String ?? "N/A"
334
+ let transactionId = data["transaction_id"] as? String ?? "N/A"
335
+ let subscriptionId = data["subscription_id"] as? String ?? "N/A"
336
+ let createdAt = data["created_at"] as? String ?? ""
337
+ let status = data["status"] as? String ?? ""
338
+
339
+ let formattedDate: String
340
+ if let date = self.convertToDate(dateString: createdAt) {
341
+ formattedDate = self.formatDate(date: date)
342
+ } else {
343
+ formattedDate = "N/A"
344
+ }
345
+
346
+ self.lblChargeId.text = "\(chargeId)"
347
+ self.lblTransactionId.text = "\(transactionId)"
348
+ self.lblSubscriptionId.text = "\(subscriptionId)"
349
+ self.lblDate.text = "\(formattedDate)"
350
+ // self.lblTotal.text = "$\(self.amount ?? 0)"
351
+ let rawAmount = Double(self.request?.amount ?? 0)
352
+ let amountText = String(format: "$%.2f", rawAmount)
353
+ self.lblTotal.text = "\(amountText)"
354
+ self.lblRefferenceId.text = "\(refToken)"
355
+ self.lblStatus.text = "\(status)"
356
+ }
357
+ }
358
+ }
359
+ }
360
+ oneMinuteCountdownTimer?.resume()
361
+
362
+ // API check timer: runs continuously every 5 seconds until success/failure
363
+ apiStatusCheckTimer = Timer.scheduledTimer(withTimeInterval: 5.0, repeats: true) { [weak self] _ in
364
+ guard let self = self else { return }
365
+ self.checkThreeDSecureStatus(completion: nil)
366
+ }
367
+ }
368
+
369
+ @IBAction func actionBtnDone(_ sender: UIButton) {
370
+ var resultBillingInfo: [String: Any]? = nil
371
+ var resultAdditionalInfo: [String: Any]? = nil
372
+
373
+ // Extract last 4 digits and format as ****4242
374
+ if let cardNumber = cardApiParams?["card_number"] as? String, cardNumber.count >= 4 {
375
+ let last4 = String(cardNumber.suffix(4))
376
+ let masked = "****\(last4)"
377
+ chargeData["card_number_last_4"] = masked
378
+ }
379
+
380
+ // Assign billing info if non-empty
381
+ if let billing = billingInfo, !billing.isEmpty {
382
+ resultBillingInfo = billing
383
+ }
384
+
385
+ // Combine 3DS status into additionalInfo if needed
386
+ var combinedAdditionalInfo = additionalInfo ?? [:]
387
+ if let threeDS = threeDSecureStatusResponse {
388
+ combinedAdditionalInfo["threeDSecureStatus"] = threeDS
389
+ }
390
+
391
+ if !combinedAdditionalInfo.isEmpty {
392
+ resultAdditionalInfo = combinedAdditionalInfo
393
+ }
394
+
395
+ //Pass updated chargeData with masked_card_number
396
+ let result = SDKResult(
397
+ type: .success,
398
+ chargeData: chargeData,
399
+ billingInfo: resultBillingInfo,
400
+ additionalInfo: resultAdditionalInfo
401
+ )
402
+
403
+ // Notify delegate and dismiss
404
+ easyPayDelegate?.easyPayController(self.navigationController as! EasyPayViewController, didFinishWith: result)
405
+
406
+ if let easyPayVC = self.navigationController as? EasyPayViewController {
407
+ easyPayVC.dismiss(animated: true, completion: {
408
+ if let delegate = easyPayVC.easyPayDelegate {
409
+ UserStoreSingleton.shared.isLoggedIn = false
410
+ delegate.easyPayController(easyPayVC, didFinishWith: result)
411
+ }
412
+ })
413
+ }
414
+ }
415
+
416
+ func uiFinishingTouchElements() {
417
+ // Set background color for the main view
418
+ if let containerBGcolor = UserStoreSingleton.shared.container_bg_col,
419
+ let uiColor = UIColor(hex: containerBGcolor) {
420
+ self.view.backgroundColor = uiColor
421
+ self.viewMain.backgroundColor = uiColor
422
+ self.imgViewLoading.backgroundColor = uiColor
423
+ // self.viewOverlay.backgroundColor = uiColor.withAlphaComponent(0.3)
424
+ }
425
+
426
+ if let primaryBtnBackGroundColor = UserStoreSingleton.shared.primary_btn_bg_col,
427
+ let uiColor = UIColor(hex: primaryBtnBackGroundColor) {
428
+ btnDone.backgroundColor = uiColor
429
+ imgViewLoading.image = UIImage(systemName: "arrow.2.circlepath")?.withRenderingMode(.alwaysTemplate)
430
+ imgViewLoading.tintColor = uiColor
431
+
432
+ btnContinue.backgroundColor = uiColor
433
+ }
434
+
435
+ if let primaryBtnFontColor = UserStoreSingleton.shared.primary_btn_font_col,
436
+ let secondaryUIColor = UIColor(hex: primaryBtnFontColor) {
437
+ btnDone.setTitleColor(secondaryUIColor, for: .normal)
438
+ btnContinue.setTitleColor(secondaryUIColor, for: .normal)
439
+ }
440
+
441
+ if let primaryFontColor = UserStoreSingleton.shared.primary_font_col,
442
+ let uiColor = UIColor(hex: primaryFontColor) {
443
+ lblDateHeading.textColor = uiColor
444
+ lblTransactionHeading.textColor = uiColor
445
+ lblSubscriptionHeading.textColor = uiColor
446
+ lblChargeIdHeading.textColor = uiColor
447
+ lblReferenceIdHeading.textColor = uiColor
448
+ lblPaymentMethodHeading.textColor = uiColor
449
+ lblTotalHeading.textColor = uiColor
450
+ lblDate.textColor = uiColor
451
+ lblTransactionId.textColor = uiColor
452
+ lblSubscriptionId.textColor = uiColor
453
+ lblChargeId.textColor = uiColor
454
+ lblTotal.textColor = uiColor
455
+ lblRefferenceId.textColor = uiColor
456
+ lblPaymentMethodValue.textColor = uiColor
457
+ lblStatusHeading.textColor = uiColor
458
+ lblStatus.textColor = uiColor
459
+ }
460
+
461
+ if let borderRadiusString = UserStoreSingleton.shared.border_radious,
462
+ let borderRadius = Double(borderRadiusString) { // Convert String to Double
463
+ btnDone.layer.cornerRadius = CGFloat(borderRadius) // Set corner radius
464
+ } else {
465
+ btnDone.layer.cornerRadius = 8 // Default value
466
+ }
467
+ btnDone.layer.masksToBounds = true // Ensure the corners are clipped properly
468
+
469
+ if let primaryFontColor = UserStoreSingleton.shared.primary_font_col,
470
+ let uiColor = UIColor(hex: primaryFontColor) {
471
+ lblCompleteAuthentication.textColor = uiColor
472
+ lblBillingInfoData.textColor = uiColor
473
+ }
474
+
475
+ if let fontSizeString = UserStoreSingleton.shared.fontSize,
476
+ let fontSizeDouble = Double(fontSizeString) { // Convert String to Double
477
+ let fontSize = CGFloat(fontSizeDouble) // Convert Double to CGFloat
478
+ lblCompleteAuthentication.font = UIFont.systemFont(ofSize: fontSize, weight: .semibold)
479
+ // lblPaymentLink.font = UIFont.systemFont(ofSize: fontSize, weight: .medium)
480
+ lblBillingInfoData.font = UIFont.systemFont(ofSize: fontSize, weight: .medium)
481
+ }
482
+
483
+ }
484
+
485
+ private func convertToDate(dateString: String) -> Date? {
486
+ let formatter = DateFormatter()
487
+ formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
488
+ formatter.timeZone = TimeZone(abbreviation: "UTC")
489
+ return formatter.date(from: dateString)
490
+ }
491
+
492
+ private func formatDate(date: Date) -> String {
493
+ let formatter = DateFormatter()
494
+ formatter.dateFormat = "dd/MM/yyyy, HH:mm:ss"
495
+ formatter.timeZone = TimeZone.current
496
+ return formatter.string(from: date)
497
+ }
498
+
499
+ // MARK: - GET Transaction Status API
500
+ private func checkThreeDSecureStatus(completion: (() -> Void)?) {
501
+ guard let secureToken = chargeData["secure_token"] as? String else {
502
+ print("Secure token not found in chargeData")
503
+ completion?()
504
+ return
505
+ }
506
+
507
+ let urlString = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.threeDSecureStatus(secureToken).path()
508
+ print("Final 3DS status URL: \(urlString)")
509
+
510
+ guard let url = URL(string: urlString) else {
511
+ print("Invalid URL")
512
+ completion?()
513
+ return
514
+ }
515
+
516
+ var uRLRequest = URLRequest(url: url)
517
+ uRLRequest.httpMethod = "GET"
518
+ uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
519
+
520
+ if let token = UserStoreSingleton.shared.customerToken {
521
+ uRLRequest.addValue(token, forHTTPHeaderField: "Customer-Token")
522
+ }
523
+
524
+ if let apiKey = EnvironmentConfig.apiKey,
525
+ let apiSecret = EnvironmentConfig.apiSecret {
526
+ uRLRequest.addValue(apiKey, forHTTPHeaderField: "x-api-key")
527
+ uRLRequest.addValue(apiSecret, forHTTPHeaderField: "x-api-secret")
528
+ }
529
+
530
+ let task = URLSession.shared.dataTask(with: uRLRequest) { [weak self] data, response, error in
531
+ defer { completion?() }
532
+
533
+ guard let self = self else { return }
534
+
535
+ if let error = error {
536
+ print("3DS status check error:", error.localizedDescription)
537
+ return
538
+ }
539
+
540
+ guard let data = data else {
541
+ print("No data in response")
542
+ return
543
+ }
544
+
545
+ do {
546
+ if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] {
547
+ print("3DS status response:", json)
548
+ self.threeDSecureStatusResponse = json
549
+
550
+ if let dataDict = json["data"] as? [String: Any] {
551
+ DispatchQueue.main.async {
552
+ let chargeId = dataDict["charge_id"] as? String ?? "N/A"
553
+ let transactionId = dataDict["transaction_id"] as? String ?? "N/A"
554
+ let subscriptionId = dataDict["subscription_id"] as? String ?? "N/A"
555
+ let createdAt = dataDict["created_at"] as? String ?? ""
556
+ let referenceId = dataDict["ref_token"] as? String ?? "N/A"
557
+ let status = dataDict["status"] as? String ?? "N/A"
558
+
559
+ let formattedDate: String
560
+ if let date = self.convertToDate(dateString: createdAt) {
561
+ formattedDate = self.formatDate(date: date)
562
+ } else {
563
+ formattedDate = "N/A"
564
+ }
565
+
566
+ self.lblChargeId.text = chargeId
567
+ self.lblTransactionId.text = transactionId
568
+ self.lblSubscriptionId.text = subscriptionId
569
+ self.lblDate.text = formattedDate
570
+ // self.lblTotal.text = "$\(self.amount ?? 0)"
571
+ let rawAmount = Double(self.request?.amount ?? 0)
572
+ let amountText = String(format: "$%.2f", rawAmount)
573
+ self.lblTotal.text = "\(amountText)"
574
+
575
+ self.lblRefferenceId.text = referenceId
576
+ self.lblStatus.text = status
577
+
578
+ if status.lowercased() == "failed" {
579
+ self.imgViewPaymentDone.image = UIImage(named: "payment_error_icon", in: .easyPayBundle, compatibleWith: nil)
580
+ self.imgViewPaymentDone.tintColor = .systemRed
581
+ self.apiStatusCheckTimer?.invalidate()
582
+ self.apiStatusCheckTimer = nil
583
+ self.oneMinuteCountdownTimer?.cancel()
584
+ self.oneMinuteCountdownTimer = nil
585
+
586
+ self.imgViewLoading.layer.removeAllAnimations()
587
+ self.imgViewLoading.isHidden = true
588
+ self.imgViewPaymentDone.isHidden = false
589
+ self.lblCompleteAuthentication.text = "Payment Failed"
590
+ self.btnDone.isHidden = false
591
+ // self.lblPaymentLink.isHidden = false
592
+ self.viewPaymentDetails.isHidden = false
593
+
594
+ self.btnContinue.isHidden = true
595
+ }
596
+ else if status.lowercased() == "completed" {
597
+ // Success case
598
+ self.imgViewPaymentDone.image = UIImage(named: "payment_done_icon", in: .easyPayBundle, compatibleWith: nil)
599
+ self.apiStatusCheckTimer?.invalidate()
600
+ self.apiStatusCheckTimer = nil
601
+ self.oneMinuteCountdownTimer?.cancel()
602
+ self.oneMinuteCountdownTimer = nil
603
+
604
+ self.imgViewLoading.layer.removeAllAnimations()
605
+ self.imgViewLoading.isHidden = true
606
+ self.imgViewPaymentDone.isHidden = false
607
+ self.lblCompleteAuthentication.text = "Authentication Completed"
608
+ self.btnDone.isHidden = false
609
+ // self.lblPaymentLink.isHidden = false
610
+ self.viewPaymentDetails.isHidden = false
611
+
612
+ self.btnContinue.isHidden = true
613
+ } else {
614
+ // Still waiting
615
+ self.imgViewPaymentDone.image = UIImage(systemName: "exclamationmark.circle.fill")
616
+ self.imgViewPaymentDone.tintColor = .systemRed
617
+ }
618
+ }
619
+ }
620
+ }
621
+ } catch {
622
+ print("Error parsing JSON:", error.localizedDescription)
623
+ }
624
+ }
625
+ task.resume()
626
+ }
627
+
628
+ }
629
+
@@ -1,6 +1,6 @@
1
1
  Pod::Spec.new do |s|
2
2
  s.name = 'easymerchantsdk'
3
- s.version = '2.2.2'
3
+ s.version = '2.2.3'
4
4
  s.summary = 'A React Native SDK for Easy Merchant.'
5
5
  s.description = <<-DESC
6
6
  A React Native SDK to enable Easy Merchant functionality in mobile applications.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jimrising/easymerchantsdk-react-native",
3
- "version": "2.2.2",
3
+ "version": "2.2.3",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {