@jimrising/easymerchantsdk-react-native 2.3.9 → 2.4.1
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.
- package/.idea/caches/deviceStreaming.xml +340 -0
- package/.idea/em-MobileCheckoutSDK-ReactNative.iml +9 -0
- package/.idea/misc.xml +5 -0
- package/.idea/modules.xml +8 -0
- package/.idea/vcs.xml +6 -0
- package/README.md +113 -42
- package/android/build/generated/source/buildConfig/debug/com/reactlibrary/BuildConfig.java +10 -0
- package/android/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/AndroidManifest.xml +7 -0
- package/android/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/output-metadata.json +18 -0
- package/android/build/intermediates/aar_metadata/debug/writeDebugAarMetadata/aar-metadata.properties +6 -0
- package/android/build/intermediates/annotation_processor_list/debug/javaPreCompileDebug/annotationProcessors.json +1 -0
- package/android/build/intermediates/compile_library_classes_jar/debug/bundleLibCompileToJarDebug/classes.jar +0 -0
- package/android/build/intermediates/compile_r_class_jar/debug/generateDebugRFile/R.jar +0 -0
- package/android/build/intermediates/compile_symbol_list/debug/generateDebugRFile/R.txt +0 -0
- package/android/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties +1 -0
- package/android/build/intermediates/incremental/debug/packageDebugResources/merger.xml +2 -0
- package/android/build/intermediates/incremental/mergeDebugAssets/merger.xml +2 -0
- package/android/build/intermediates/incremental/mergeDebugJniLibFolders/merger.xml +2 -0
- package/android/build/intermediates/incremental/mergeDebugShaders/merger.xml +2 -0
- package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/BuildConfig.class +0 -0
- package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkModule$1$1.class +0 -0
- package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkModule$1.class +0 -0
- package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkModule$2.class +0 -0
- package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkModule$3.class +0 -0
- package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkModule.class +0 -0
- package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkPackage.class +0 -0
- package/android/build/intermediates/local_only_symbol_list/debug/parseDebugLocalResources/R-def.txt +2 -0
- package/android/build/intermediates/manifest_merge_blame_file/debug/processDebugManifest/manifest-merger-blame-debug-report.txt +7 -0
- package/android/build/intermediates/merged_manifest/debug/processDebugManifest/AndroidManifest.xml +7 -0
- package/android/build/intermediates/navigation_json/debug/extractDeepLinksDebug/navigation.json +1 -0
- package/android/build/intermediates/nested_resources_validation_report/debug/generateDebugResources/nestedResourcesValidationReport.txt +1 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/BuildConfig.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/RNEasymerchantsdkModule$1$1.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/RNEasymerchantsdkModule$1.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/RNEasymerchantsdkModule$2.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/RNEasymerchantsdkModule$3.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/RNEasymerchantsdkModule.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/RNEasymerchantsdkPackage.class +0 -0
- package/android/build/intermediates/runtime_library_classes_jar/debug/bundleLibRuntimeToJarDebug/classes.jar +0 -0
- package/android/build/intermediates/symbol_list_with_package_name/debug/generateDebugRFile/package-aware-r.txt +1 -0
- package/android/build/outputs/logs/manifest-merger-debug-report.txt +16 -0
- package/android/build/tmp/compileDebugJavaWithJavac/previous-compilation-data.bin +0 -0
- package/android/build.gradle +3 -2
- package/ios/Classes/EasyMerchantSdk.h +4 -0
- package/ios/Classes/EasyMerchantSdk.m +8 -2
- package/ios/Classes/EasyMerchantSdk.swift +14 -0
- package/ios/Helper/GrailPayHelper.swift +166 -5
- package/ios/Pods/UserDefaults/UserStoreSingleton.swift +425 -0
- package/ios/Pods/ViewControllers/AdditionalInfoVC.swift +2996 -0
- package/ios/Pods/ViewControllers/BaseVC.swift +142 -0
- package/ios/Pods/ViewControllers/BillingInfoVC/BillingInfoVC.swift +3807 -0
- package/ios/Pods/ViewControllers/BillingInfoVC/Cells/CityListTVC.swift +46 -0
- package/ios/Pods/ViewControllers/BillingInfoVC/Cells/CountryListTVC.swift +47 -0
- package/ios/Pods/ViewControllers/BillingInfoVC/Cells/StateListTVC.swift +46 -0
- package/ios/Pods/ViewControllers/Clean Runner_2025-07-23T14-58-05.txt +13 -0
- package/ios/Pods/ViewControllers/CountryListVC.swift +435 -0
- package/ios/Pods/ViewControllers/EmailVerificationVC.swift +300 -0
- package/ios/Pods/ViewControllers/GrailPayVC.swift +492 -0
- package/ios/Pods/ViewControllers/OTPVerificationVC.swift +2278 -0
- package/ios/Pods/ViewControllers/PaymentDoneVC.swift +287 -0
- package/ios/Pods/ViewControllers/PaymentErrorVC.swift +85 -0
- package/ios/Pods/ViewControllers/PaymentInformation/AccountTypeTVC.swift +41 -0
- package/ios/Pods/ViewControllers/PaymentInformation/PaymentInfoVC.swift +13115 -0
- package/ios/Pods/ViewControllers/PaymentInformation/PaymentInformationCVC.swift +35 -0
- package/ios/Pods/ViewControllers/PaymentInformation/RecurringTVC.swift +40 -0
- package/ios/Pods/ViewControllers/PaymentInformation/SavedAccountsTVC/SavedAccountTVC.swift +80 -0
- package/ios/Pods/ViewControllers/PaymentInformation/SavedAccountsTVC/SavedAccountTVC.xib +163 -0
- package/ios/Pods/ViewControllers/PaymentInformation/SavedCardsTVC/SavedCardsTVC.swift +81 -0
- package/ios/Pods/ViewControllers/PaymentInformation/SavedCardsTVC/SavedCardsTVC.xib +188 -0
- package/ios/Pods/ViewControllers/PaymentStatusWebViewVC.swift +167 -0
- package/ios/Pods/ViewControllers/TermAndConditionsVC.swift +63 -0
- package/ios/Pods/ViewControllers/ThreeDSecurePaymentDoneVC.swift +1254 -0
- package/ios/easymerchantsdk.podspec +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,1254 @@
|
|
|
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 = 180 // 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 viewWillDisappear(_ animated: Bool) {
|
|
153
|
+
// super.viewWillDisappear(animated)
|
|
154
|
+
// apiStatusCheckTimer?.invalidate()
|
|
155
|
+
// apiStatusCheckTimer = nil
|
|
156
|
+
// oneMinuteCountdownTimer?.cancel() // Cancel the dispatch source timer
|
|
157
|
+
// oneMinuteCountdownTimer = nil
|
|
158
|
+
// }
|
|
159
|
+
//
|
|
160
|
+
// private func showHourglassGIF() {
|
|
161
|
+
// guard let bundlePath = Bundle.easyPayBundle.path(forResource: "hourglass", ofType: "gif"),
|
|
162
|
+
// let gifData = try? Data(contentsOf: URL(fileURLWithPath: bundlePath)),
|
|
163
|
+
// let source = CGImageSourceCreateWithData(gifData as CFData, nil) else {
|
|
164
|
+
// print("⚠️ Could not load GIF from bundle")
|
|
165
|
+
// return
|
|
166
|
+
// }
|
|
167
|
+
//
|
|
168
|
+
// var images: [UIImage] = []
|
|
169
|
+
// var duration: Double = 0
|
|
170
|
+
//
|
|
171
|
+
// let frameCount = CGImageSourceGetCount(source)
|
|
172
|
+
// for i in 0..<frameCount {
|
|
173
|
+
// if let cgImage = CGImageSourceCreateImageAtIndex(source, i, nil) {
|
|
174
|
+
// let frameDuration = getFrameDuration(from: source, index: i)
|
|
175
|
+
// duration += frameDuration
|
|
176
|
+
// images.append(UIImage(cgImage: cgImage))
|
|
177
|
+
// }
|
|
178
|
+
// }
|
|
179
|
+
//
|
|
180
|
+
// if !images.isEmpty {
|
|
181
|
+
// let animatedImage = UIImage.animatedImage(with: images, duration: duration)
|
|
182
|
+
// imgViewLoading.image = animatedImage
|
|
183
|
+
// imgViewLoading.contentMode = .scaleToFill
|
|
184
|
+
// }
|
|
185
|
+
// }
|
|
186
|
+
//
|
|
187
|
+
// private func getFrameDuration(from source: CGImageSource, index: Int) -> Double {
|
|
188
|
+
// let defaultFrameDuration = 0.1
|
|
189
|
+
//
|
|
190
|
+
// guard let properties = CGImageSourceCopyPropertiesAtIndex(source, index, nil) as? [CFString: Any],
|
|
191
|
+
// let gifProperties = properties[kCGImagePropertyGIFDictionary] as? [CFString: Any] else {
|
|
192
|
+
// return defaultFrameDuration
|
|
193
|
+
// }
|
|
194
|
+
//
|
|
195
|
+
// if let unclampedDelay = gifProperties[kCGImagePropertyGIFUnclampedDelayTime] as? Double, unclampedDelay > 0 {
|
|
196
|
+
// return unclampedDelay
|
|
197
|
+
// }
|
|
198
|
+
//
|
|
199
|
+
// if let delay = gifProperties[kCGImagePropertyGIFDelayTime] as? Double, delay > 0 {
|
|
200
|
+
// return delay
|
|
201
|
+
// }
|
|
202
|
+
//
|
|
203
|
+
// return defaultFrameDuration
|
|
204
|
+
// }
|
|
205
|
+
//
|
|
206
|
+
// func startRotatingImage() {
|
|
207
|
+
// showHourglassGIF()
|
|
208
|
+
// }
|
|
209
|
+
//
|
|
210
|
+
// @IBAction func actionBtnContinue(_ sender: UIButton) {
|
|
211
|
+
// guard let urlStr = redirectURL, !urlStr.isEmpty else {
|
|
212
|
+
// print("Redirect URL is nil or empty")
|
|
213
|
+
// return
|
|
214
|
+
// }
|
|
215
|
+
//
|
|
216
|
+
// didTapContinue = true
|
|
217
|
+
// lblCompleteAuthentication.text = "Wating for 3DS Authentication..."
|
|
218
|
+
// btnContinue.isHidden = true
|
|
219
|
+
//
|
|
220
|
+
// let vc = easymerchantsdk.instantiateViewController(withIdentifier: "PaymentStatusWebViewVC") as! PaymentStatusWebViewVC
|
|
221
|
+
// vc.urlString = urlStr // Pass the URL to WebView VC
|
|
222
|
+
// self.navigationController?.pushViewController(vc, animated: true)
|
|
223
|
+
// }
|
|
224
|
+
//
|
|
225
|
+
// private func startOneMinuteCountdownAndAPICheck() {
|
|
226
|
+
// startRotatingImage()
|
|
227
|
+
// imgViewPaymentDone.isHidden = true
|
|
228
|
+
// imgViewLoading.isHidden = false
|
|
229
|
+
// // lblCompleteAuthentication.text = "Click the link below to complete 3DS Authentication.!"
|
|
230
|
+
// if !didTapContinue {
|
|
231
|
+
// lblCompleteAuthentication.text = "Tap Continue to proceed with 3D Secure authentication."
|
|
232
|
+
// }
|
|
233
|
+
// btnDone.isHidden = true
|
|
234
|
+
//
|
|
235
|
+
// apiStatusCheckTimer?.invalidate()
|
|
236
|
+
// apiStatusCheckTimer = nil
|
|
237
|
+
//
|
|
238
|
+
// initialAPIGroup.enter()
|
|
239
|
+
// checkThreeDSecureStatus { [weak self] in
|
|
240
|
+
// self?.initialAPIGroup.leave()
|
|
241
|
+
// }
|
|
242
|
+
//
|
|
243
|
+
// countdownRemaining = 180
|
|
244
|
+
//
|
|
245
|
+
// oneMinuteCountdownTimer = DispatchSource.makeTimerSource(queue: .main)
|
|
246
|
+
// oneMinuteCountdownTimer?.schedule(deadline: .now(), repeating: .seconds(1))
|
|
247
|
+
// oneMinuteCountdownTimer?.setEventHandler { [weak self] in
|
|
248
|
+
// guard let self = self else { return }
|
|
249
|
+
//
|
|
250
|
+
// self.countdownRemaining -= 1
|
|
251
|
+
// if self.countdownRemaining <= 0 {
|
|
252
|
+
// self.oneMinuteCountdownTimer?.cancel()
|
|
253
|
+
// self.oneMinuteCountdownTimer = nil
|
|
254
|
+
//
|
|
255
|
+
// DispatchQueue.main.async {
|
|
256
|
+
// self.imgViewLoading.layer.removeAllAnimations()
|
|
257
|
+
// self.imgViewLoading.isHidden = true
|
|
258
|
+
// self.imgViewPaymentDone.isHidden = false
|
|
259
|
+
// // self.lblCompleteAuthentication.text = "Click the link below to complete 3DS Authentication.!"
|
|
260
|
+
// if !self.didTapContinue {
|
|
261
|
+
// self.lblCompleteAuthentication.text = "Tap Continue to proceed with 3D Secure authentication."
|
|
262
|
+
// }
|
|
263
|
+
// self.btnDone.isHidden = false
|
|
264
|
+
// // self.lblPaymentLink.isHidden = false
|
|
265
|
+
// self.viewPaymentDetails.isHidden = false
|
|
266
|
+
//
|
|
267
|
+
// if let json = self.threeDSecureStatusResponse,
|
|
268
|
+
// let data = json["data"] as? [String: Any],
|
|
269
|
+
// let chargeId = data["charge_id"],
|
|
270
|
+
// chargeId is NSNull || (chargeId as? String ?? "").isEmpty {
|
|
271
|
+
// self.imgViewPaymentDone.image = UIImage(systemName: "exclamationmark.circle.fill")
|
|
272
|
+
// self.imgViewPaymentDone.tintColor = .systemRed
|
|
273
|
+
//
|
|
274
|
+
// let refToken = data["ref_token"] as? String ?? "N/A"
|
|
275
|
+
// let chargeId = data["charge_id"] as? String ?? "N/A"
|
|
276
|
+
// let transactionId = data["transaction_id"] as? String ?? "N/A"
|
|
277
|
+
// let subscriptionId = data["subscription_id"] as? String ?? "N/A"
|
|
278
|
+
// let createdAt = data["created_at"] as? String ?? ""
|
|
279
|
+
// let status = data["status"] as? String ?? ""
|
|
280
|
+
//
|
|
281
|
+
// let formattedDate: String
|
|
282
|
+
// if let date = self.convertToDate(dateString: createdAt) {
|
|
283
|
+
// formattedDate = self.formatDate(date: date)
|
|
284
|
+
// } else {
|
|
285
|
+
// formattedDate = "N/A"
|
|
286
|
+
// }
|
|
287
|
+
//
|
|
288
|
+
// self.lblChargeId.text = "\(chargeId)"
|
|
289
|
+
// self.lblTransactionId.text = "\(transactionId)"
|
|
290
|
+
// self.lblSubscriptionId.text = "\(subscriptionId)"
|
|
291
|
+
// self.lblDate.text = "\(formattedDate)"
|
|
292
|
+
// // self.lblTotal.text = "$\(self.amount ?? 0)"
|
|
293
|
+
// let rawAmount = Double(self.request?.amount ?? 0)
|
|
294
|
+
// let amountText = String(format: "$%.2f", rawAmount)
|
|
295
|
+
// self.lblTotal.text = "\(amountText)"
|
|
296
|
+
// self.lblRefferenceId.text = "\(refToken)"
|
|
297
|
+
// self.lblStatus.text = "\(status)"
|
|
298
|
+
// }
|
|
299
|
+
// }
|
|
300
|
+
// }
|
|
301
|
+
// }
|
|
302
|
+
// oneMinuteCountdownTimer?.resume()
|
|
303
|
+
//
|
|
304
|
+
// // API check timer: runs continuously every 5 seconds until success/failure
|
|
305
|
+
// apiStatusCheckTimer = Timer.scheduledTimer(withTimeInterval: 5.0, repeats: true) { [weak self] _ in
|
|
306
|
+
// guard let self = self else { return }
|
|
307
|
+
// self.checkThreeDSecureStatus(completion: nil)
|
|
308
|
+
// }
|
|
309
|
+
// }
|
|
310
|
+
//
|
|
311
|
+
// @IBAction func actionBtnDone(_ sender: UIButton) {
|
|
312
|
+
// var resultBillingInfo: [String: Any]? = nil
|
|
313
|
+
// // var resultAdditionalInfo: [String: Any]? = nil
|
|
314
|
+
//
|
|
315
|
+
// // Extract last 4 digits and format as ****4242
|
|
316
|
+
// if let cardNumber = cardApiParams?["card_number"] as? String, cardNumber.count >= 4 {
|
|
317
|
+
// let last4 = String(cardNumber.suffix(4))
|
|
318
|
+
// let masked = "****\(last4)"
|
|
319
|
+
// chargeData["card_number_last_4"] = masked
|
|
320
|
+
// }
|
|
321
|
+
//
|
|
322
|
+
// // Assign billing info if non-empty
|
|
323
|
+
// if let billing = billingInfo, !billing.isEmpty {
|
|
324
|
+
// resultBillingInfo = billing
|
|
325
|
+
// }
|
|
326
|
+
//
|
|
327
|
+
// // Create response dictionary
|
|
328
|
+
// var response: [String: Any] = [:]
|
|
329
|
+
//
|
|
330
|
+
// // Add 3DS status response data if available, otherwise use charge data
|
|
331
|
+
// if let threeDS = threeDSecureStatusResponse {
|
|
332
|
+
// // Add all 3DS response data directly, preserving the "data" key structure
|
|
333
|
+
// for (key, value) in threeDS {
|
|
334
|
+
// response[key] = value
|
|
335
|
+
// }
|
|
336
|
+
// } else {
|
|
337
|
+
// // Fallback to older charge data if 3DS status response is not available
|
|
338
|
+
// for (key, value) in chargeData {
|
|
339
|
+
// response[key] = value
|
|
340
|
+
// }
|
|
341
|
+
// }
|
|
342
|
+
//
|
|
343
|
+
// // Add billing info if available
|
|
344
|
+
// if let billing = resultBillingInfo {
|
|
345
|
+
// response["billingInfo"] = billing
|
|
346
|
+
// }
|
|
347
|
+
//
|
|
348
|
+
// // Add additional info if available
|
|
349
|
+
// if let additional = additionalInfo {
|
|
350
|
+
// response["additionalInfo"] = additional
|
|
351
|
+
// }
|
|
352
|
+
//
|
|
353
|
+
// // Add card number last 4 if available (from chargeData) - only if not already in response
|
|
354
|
+
// if let last4 = chargeData["card_number_last_4"] as? String, response["card_number_last_4"] == nil {
|
|
355
|
+
// response["card_number_last_4"] = last4
|
|
356
|
+
// }
|
|
357
|
+
//
|
|
358
|
+
// // Create SDK result with combined data
|
|
359
|
+
// let result = SDKResult(type: .success, data: response as NSDictionary)
|
|
360
|
+
//
|
|
361
|
+
// // Notify delegate and dismiss
|
|
362
|
+
// easyPayDelegate?.easyPayController(self.navigationController as! EasyPayViewController, didFinishWith: result)
|
|
363
|
+
//
|
|
364
|
+
// if let easyPayVC = self.navigationController as? EasyPayViewController {
|
|
365
|
+
// easyPayVC.dismiss(animated: true, completion: {
|
|
366
|
+
// if let delegate = easyPayVC.easyPayDelegate {
|
|
367
|
+
// UserStoreSingleton.shared.isLoggedIn = false
|
|
368
|
+
// delegate.easyPayController(easyPayVC, didFinishWith: result)
|
|
369
|
+
// }
|
|
370
|
+
// })
|
|
371
|
+
// }
|
|
372
|
+
// }
|
|
373
|
+
//
|
|
374
|
+
// func uiFinishingTouchElements() {
|
|
375
|
+
// // Set background color for the main view
|
|
376
|
+
// if let containerBGcolor = UserStoreSingleton.shared.container_bg_col,
|
|
377
|
+
// let uiColor = UIColor(hex: containerBGcolor) {
|
|
378
|
+
// self.view.backgroundColor = uiColor
|
|
379
|
+
// self.viewMain.backgroundColor = uiColor
|
|
380
|
+
// self.imgViewLoading.backgroundColor = uiColor
|
|
381
|
+
// // self.viewOverlay.backgroundColor = uiColor.withAlphaComponent(0.3)
|
|
382
|
+
// }
|
|
383
|
+
//
|
|
384
|
+
// if let primaryBtnBackGroundColor = UserStoreSingleton.shared.primary_btn_bg_col,
|
|
385
|
+
// let uiColor = UIColor(hex: primaryBtnBackGroundColor) {
|
|
386
|
+
// btnDone.backgroundColor = uiColor
|
|
387
|
+
// imgViewLoading.image = UIImage(systemName: "arrow.2.circlepath")?.withRenderingMode(.alwaysTemplate)
|
|
388
|
+
// imgViewLoading.tintColor = uiColor
|
|
389
|
+
//
|
|
390
|
+
// btnContinue.backgroundColor = uiColor
|
|
391
|
+
// }
|
|
392
|
+
//
|
|
393
|
+
// if let primaryBtnFontColor = UserStoreSingleton.shared.primary_btn_font_col,
|
|
394
|
+
// let secondaryUIColor = UIColor(hex: primaryBtnFontColor) {
|
|
395
|
+
// btnDone.setTitleColor(secondaryUIColor, for: .normal)
|
|
396
|
+
// btnContinue.setTitleColor(secondaryUIColor, for: .normal)
|
|
397
|
+
// }
|
|
398
|
+
//
|
|
399
|
+
// if let primaryFontColor = UserStoreSingleton.shared.primary_font_col,
|
|
400
|
+
// let uiColor = UIColor(hex: primaryFontColor) {
|
|
401
|
+
// lblDateHeading.textColor = uiColor
|
|
402
|
+
// lblTransactionHeading.textColor = uiColor
|
|
403
|
+
// lblSubscriptionHeading.textColor = uiColor
|
|
404
|
+
// lblChargeIdHeading.textColor = uiColor
|
|
405
|
+
// lblReferenceIdHeading.textColor = uiColor
|
|
406
|
+
// lblPaymentMethodHeading.textColor = uiColor
|
|
407
|
+
// lblTotalHeading.textColor = uiColor
|
|
408
|
+
// lblDate.textColor = uiColor
|
|
409
|
+
// lblTransactionId.textColor = uiColor
|
|
410
|
+
// lblSubscriptionId.textColor = uiColor
|
|
411
|
+
// lblChargeId.textColor = uiColor
|
|
412
|
+
// lblTotal.textColor = uiColor
|
|
413
|
+
// lblRefferenceId.textColor = uiColor
|
|
414
|
+
// lblPaymentMethodValue.textColor = uiColor
|
|
415
|
+
// lblStatusHeading.textColor = uiColor
|
|
416
|
+
// lblStatus.textColor = uiColor
|
|
417
|
+
// }
|
|
418
|
+
//
|
|
419
|
+
// if let borderRadiusString = UserStoreSingleton.shared.border_radious,
|
|
420
|
+
// let borderRadius = Double(borderRadiusString) { // Convert String to Double
|
|
421
|
+
// btnDone.layer.cornerRadius = CGFloat(borderRadius) // Set corner radius
|
|
422
|
+
// } else {
|
|
423
|
+
// btnDone.layer.cornerRadius = 8 // Default value
|
|
424
|
+
// }
|
|
425
|
+
// btnDone.layer.masksToBounds = true // Ensure the corners are clipped properly
|
|
426
|
+
//
|
|
427
|
+
// if let primaryFontColor = UserStoreSingleton.shared.primary_font_col,
|
|
428
|
+
// let uiColor = UIColor(hex: primaryFontColor) {
|
|
429
|
+
// lblCompleteAuthentication.textColor = uiColor
|
|
430
|
+
// lblBillingInfoData.textColor = uiColor
|
|
431
|
+
// }
|
|
432
|
+
//
|
|
433
|
+
// if let fontSizeString = UserStoreSingleton.shared.fontSize,
|
|
434
|
+
// let fontSizeDouble = Double(fontSizeString) { // Convert String to Double
|
|
435
|
+
// let fontSize = CGFloat(fontSizeDouble) // Convert Double to CGFloat
|
|
436
|
+
// // lblCompleteAuthentication.font = UIFont.systemFont(ofSize: fontSize, weight: .semibold)
|
|
437
|
+
// // lblPaymentLink.font = UIFont.systemFont(ofSize: fontSize, weight: .medium)
|
|
438
|
+
// lblBillingInfoData.font = UIFont.systemFont(ofSize: fontSize, weight: .medium)
|
|
439
|
+
// }
|
|
440
|
+
//
|
|
441
|
+
// }
|
|
442
|
+
//
|
|
443
|
+
// private func convertToDate(dateString: String) -> Date? {
|
|
444
|
+
// let formatter = DateFormatter()
|
|
445
|
+
// formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
|
|
446
|
+
// formatter.timeZone = TimeZone(abbreviation: "UTC")
|
|
447
|
+
// return formatter.date(from: dateString)
|
|
448
|
+
// }
|
|
449
|
+
//
|
|
450
|
+
// private func formatDate(date: Date) -> String {
|
|
451
|
+
// let formatter = DateFormatter()
|
|
452
|
+
// formatter.dateFormat = "dd/MM/yyyy, HH:mm:ss"
|
|
453
|
+
// formatter.timeZone = TimeZone.current
|
|
454
|
+
// return formatter.string(from: date)
|
|
455
|
+
// }
|
|
456
|
+
//
|
|
457
|
+
// // MARK: - GET Transaction Status API
|
|
458
|
+
// private func checkThreeDSecureStatus(completion: (() -> Void)?) {
|
|
459
|
+
// guard let secureToken = chargeData["secure_token"] as? String else {
|
|
460
|
+
// print("Secure token not found in chargeData")
|
|
461
|
+
// completion?()
|
|
462
|
+
// return
|
|
463
|
+
// }
|
|
464
|
+
//
|
|
465
|
+
// let urlString = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.threeDSecureStatus(secureToken).path()
|
|
466
|
+
// print("Final 3DS status URL: \(urlString)")
|
|
467
|
+
//
|
|
468
|
+
// guard let url = URL(string: urlString) else {
|
|
469
|
+
// print("Invalid URL")
|
|
470
|
+
// completion?()
|
|
471
|
+
// return
|
|
472
|
+
// }
|
|
473
|
+
//
|
|
474
|
+
// var uRLRequest = URLRequest(url: url)
|
|
475
|
+
// uRLRequest.httpMethod = "GET"
|
|
476
|
+
// uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
477
|
+
//
|
|
478
|
+
// let token = UserStoreSingleton.shared.clientToken ?? ""
|
|
479
|
+
// uRLRequest.addValue(token, forHTTPHeaderField: "clientToken")
|
|
480
|
+
//
|
|
481
|
+
// let task = URLSession.shared.dataTask(with: uRLRequest) { [weak self] data, response, error in
|
|
482
|
+
// defer { completion?() }
|
|
483
|
+
//
|
|
484
|
+
// guard let self = self else { return }
|
|
485
|
+
//
|
|
486
|
+
// if let error = error {
|
|
487
|
+
// print("3DS status check error:", error.localizedDescription)
|
|
488
|
+
// return
|
|
489
|
+
// }
|
|
490
|
+
//
|
|
491
|
+
// guard let data = data else {
|
|
492
|
+
// print("No data in response")
|
|
493
|
+
// return
|
|
494
|
+
// }
|
|
495
|
+
//
|
|
496
|
+
// do {
|
|
497
|
+
// if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] {
|
|
498
|
+
// print("3DS status response:", json)
|
|
499
|
+
// self.threeDSecureStatusResponse = json
|
|
500
|
+
//
|
|
501
|
+
// if let dataDict = json["data"] as? [String: Any] {
|
|
502
|
+
// DispatchQueue.main.async {
|
|
503
|
+
// let chargeId = dataDict["charge_id"] as? String ?? "N/A"
|
|
504
|
+
// let transactionId = dataDict["transaction_id"] as? String ?? "N/A"
|
|
505
|
+
// let subscriptionId = dataDict["subscription_id"] as? String ?? "N/A"
|
|
506
|
+
// let createdAt = dataDict["created_at"] as? String ?? ""
|
|
507
|
+
// let referenceId = dataDict["ref_token"] as? String ?? "N/A"
|
|
508
|
+
// let status = dataDict["status"] as? String ?? "N/A"
|
|
509
|
+
//
|
|
510
|
+
// let formattedDate: String
|
|
511
|
+
// if let date = self.convertToDate(dateString: createdAt) {
|
|
512
|
+
// formattedDate = self.formatDate(date: date)
|
|
513
|
+
// } else {
|
|
514
|
+
// formattedDate = "N/A"
|
|
515
|
+
// }
|
|
516
|
+
//
|
|
517
|
+
// self.lblChargeId.text = chargeId
|
|
518
|
+
// self.lblTransactionId.text = transactionId
|
|
519
|
+
// self.lblSubscriptionId.text = subscriptionId
|
|
520
|
+
// self.lblDate.text = formattedDate
|
|
521
|
+
// // self.lblTotal.text = "$\(self.amount ?? 0)"
|
|
522
|
+
// let rawAmount = Double(self.request?.amount ?? 0)
|
|
523
|
+
// let amountText = String(format: "$%.2f", rawAmount)
|
|
524
|
+
// self.lblTotal.text = "\(amountText)"
|
|
525
|
+
//
|
|
526
|
+
// self.lblRefferenceId.text = referenceId
|
|
527
|
+
// self.lblStatus.text = status
|
|
528
|
+
//
|
|
529
|
+
// if status.lowercased() == "failed" {
|
|
530
|
+
// self.imgViewPaymentDone.image = UIImage(named: "payment_error_icon", in: .easyPayBundle, compatibleWith: nil)
|
|
531
|
+
// self.imgViewPaymentDone.tintColor = .systemRed
|
|
532
|
+
// self.apiStatusCheckTimer?.invalidate()
|
|
533
|
+
// self.apiStatusCheckTimer = nil
|
|
534
|
+
// self.oneMinuteCountdownTimer?.cancel()
|
|
535
|
+
// self.oneMinuteCountdownTimer = nil
|
|
536
|
+
//
|
|
537
|
+
// self.imgViewLoading.layer.removeAllAnimations()
|
|
538
|
+
// self.imgViewLoading.isHidden = true
|
|
539
|
+
// self.imgViewPaymentDone.isHidden = false
|
|
540
|
+
// self.lblCompleteAuthentication.text = "Payment Failed"
|
|
541
|
+
// self.btnDone.isHidden = false
|
|
542
|
+
// // self.lblPaymentLink.isHidden = false
|
|
543
|
+
// self.viewPaymentDetails.isHidden = false
|
|
544
|
+
//
|
|
545
|
+
// self.btnContinue.isHidden = true
|
|
546
|
+
// }
|
|
547
|
+
// else if status.lowercased() == "completed" {
|
|
548
|
+
// // Success case
|
|
549
|
+
// self.imgViewPaymentDone.image = UIImage(named: "payment_done_icon", in: .easyPayBundle, compatibleWith: nil)
|
|
550
|
+
// self.apiStatusCheckTimer?.invalidate()
|
|
551
|
+
// self.apiStatusCheckTimer = nil
|
|
552
|
+
// self.oneMinuteCountdownTimer?.cancel()
|
|
553
|
+
// self.oneMinuteCountdownTimer = nil
|
|
554
|
+
//
|
|
555
|
+
// self.imgViewLoading.layer.removeAllAnimations()
|
|
556
|
+
// self.imgViewLoading.isHidden = true
|
|
557
|
+
// self.imgViewPaymentDone.isHidden = false
|
|
558
|
+
// self.lblCompleteAuthentication.text = "Authentication Completed"
|
|
559
|
+
// self.btnDone.isHidden = false
|
|
560
|
+
// // self.lblPaymentLink.isHidden = false
|
|
561
|
+
// self.viewPaymentDetails.isHidden = false
|
|
562
|
+
//
|
|
563
|
+
// self.btnContinue.isHidden = true
|
|
564
|
+
// } else {
|
|
565
|
+
// // Still waiting
|
|
566
|
+
// self.imgViewPaymentDone.image = UIImage(systemName: "exclamationmark.circle.fill")
|
|
567
|
+
// self.imgViewPaymentDone.tintColor = .systemRed
|
|
568
|
+
// }
|
|
569
|
+
// }
|
|
570
|
+
// }
|
|
571
|
+
// }
|
|
572
|
+
// } catch {
|
|
573
|
+
// print("Error parsing JSON:", error.localizedDescription)
|
|
574
|
+
// }
|
|
575
|
+
// }
|
|
576
|
+
// task.resume()
|
|
577
|
+
// }
|
|
578
|
+
//
|
|
579
|
+
//}
|
|
580
|
+
//
|
|
581
|
+
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
|
|
585
|
+
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
|
|
592
|
+
|
|
593
|
+
|
|
594
|
+
|
|
595
|
+
|
|
596
|
+
|
|
597
|
+
|
|
598
|
+
|
|
599
|
+
|
|
600
|
+
|
|
601
|
+
|
|
602
|
+
|
|
603
|
+
|
|
604
|
+
|
|
605
|
+
|
|
606
|
+
|
|
607
|
+
|
|
608
|
+
|
|
609
|
+
|
|
610
|
+
|
|
611
|
+
|
|
612
|
+
|
|
613
|
+
|
|
614
|
+
|
|
615
|
+
|
|
616
|
+
|
|
617
|
+
|
|
618
|
+
|
|
619
|
+
|
|
620
|
+
|
|
621
|
+
|
|
622
|
+
import UIKit
|
|
623
|
+
|
|
624
|
+
class ThreeDSecurePaymentDoneVC: BaseVC {
|
|
625
|
+
|
|
626
|
+
@IBOutlet weak var imgViewPaymentDone: UIImageView!
|
|
627
|
+
@IBOutlet weak var imgViewLoading: UIImageView!
|
|
628
|
+
@IBOutlet weak var viewMain: UIView!
|
|
629
|
+
@IBOutlet weak var lblCompleteAuthentication: UILabel!
|
|
630
|
+
@IBOutlet weak var btnDone: UIButton!
|
|
631
|
+
@IBOutlet weak var lblBillingInfoData: UILabel!
|
|
632
|
+
|
|
633
|
+
@IBOutlet weak var viewPaymentDetails: UIStackView!
|
|
634
|
+
@IBOutlet weak var lblDateHeading: UILabel!
|
|
635
|
+
@IBOutlet weak var lblTransactionHeading: UILabel!
|
|
636
|
+
@IBOutlet weak var lblSubscriptionHeading: UILabel!
|
|
637
|
+
@IBOutlet weak var lblChargeIdHeading: UILabel!
|
|
638
|
+
@IBOutlet weak var lblReferenceIdHeading: UILabel!
|
|
639
|
+
@IBOutlet weak var lblPaymentMethodHeading: UILabel!
|
|
640
|
+
@IBOutlet weak var lblTotalHeading: UILabel!
|
|
641
|
+
@IBOutlet weak var lblStatusHeading: UILabel!
|
|
642
|
+
|
|
643
|
+
@IBOutlet weak var lblDate: UILabel!
|
|
644
|
+
@IBOutlet weak var lblTransactionId: UILabel!
|
|
645
|
+
@IBOutlet weak var lblSubscriptionId: UILabel!
|
|
646
|
+
@IBOutlet weak var lblChargeId: UILabel!
|
|
647
|
+
@IBOutlet weak var lblTotal: UILabel!
|
|
648
|
+
@IBOutlet weak var lblRefferenceId: UILabel!
|
|
649
|
+
@IBOutlet weak var lblPaymentMethodValue: UILabel!
|
|
650
|
+
@IBOutlet weak var lblStatus: UILabel!
|
|
651
|
+
|
|
652
|
+
@IBOutlet weak var btnContinue: UIButton!
|
|
653
|
+
|
|
654
|
+
var easyPayDelegate: EasyPayViewControllerDelegate?
|
|
655
|
+
var redirectURL: String?
|
|
656
|
+
var chargeData: [String: Any] = [:]
|
|
657
|
+
|
|
658
|
+
var billingInfo: [String: Any]?
|
|
659
|
+
var additionalInfo: [String: Any]?
|
|
660
|
+
|
|
661
|
+
var threeDSecureStatusResponse: [String: Any]?
|
|
662
|
+
|
|
663
|
+
var apiStatusCheckTimer: Timer?
|
|
664
|
+
var oneMinuteCountdownTimer: DispatchSourceTimer?
|
|
665
|
+
var countdownRemaining: Int = 180
|
|
666
|
+
|
|
667
|
+
var additionalInfoData: [FieldItem]?
|
|
668
|
+
var billingInfoData: [FieldItem]?
|
|
669
|
+
var visibility: FieldsVisibility?
|
|
670
|
+
|
|
671
|
+
let initialAPIGroup = DispatchGroup()
|
|
672
|
+
|
|
673
|
+
var amount: Double?
|
|
674
|
+
|
|
675
|
+
var cardApiParams: [String: Any]?
|
|
676
|
+
|
|
677
|
+
var request: Request!
|
|
678
|
+
|
|
679
|
+
var didTapContinue = false
|
|
680
|
+
var apiAttemptCount: Int = 0
|
|
681
|
+
|
|
682
|
+
override func viewDidLoad() {
|
|
683
|
+
super.viewDidLoad()
|
|
684
|
+
uiFinishingTouchElements()
|
|
685
|
+
|
|
686
|
+
// Initially hide done image and show loading image
|
|
687
|
+
imgViewPaymentDone.isHidden = true
|
|
688
|
+
imgViewLoading.isHidden = false
|
|
689
|
+
lblCompleteAuthentication.text = "Completing Authentication..."
|
|
690
|
+
btnDone.isHidden = true
|
|
691
|
+
btnContinue.isHidden = false
|
|
692
|
+
|
|
693
|
+
// Populate initial label values
|
|
694
|
+
populateInitialLabels()
|
|
695
|
+
|
|
696
|
+
startRotatingImage()
|
|
697
|
+
startOneMinuteCountdownAndAPICheck()
|
|
698
|
+
|
|
699
|
+
NotificationCenter.default.addObserver(self, selector: #selector(appDidBecomeActive), name: UIApplication.didBecomeActiveNotification, object: nil)
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
deinit {
|
|
703
|
+
NotificationCenter.default.removeObserver(self)
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
@objc private func appDidBecomeActive() {
|
|
707
|
+
if !imgViewLoading.isHidden && imgViewLoading.layer.animation(forKey: "rotationAnimation") == nil {
|
|
708
|
+
startRotatingImage()
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
override func viewWillAppear(_ animated: Bool) {
|
|
713
|
+
super.viewWillAppear(animated)
|
|
714
|
+
uiFinishingTouchElements()
|
|
715
|
+
|
|
716
|
+
// Restart rotating animation if missing
|
|
717
|
+
if !imgViewLoading.isHidden && imgViewLoading.layer.animation(forKey: "rotationAnimation") == nil {
|
|
718
|
+
startRotatingImage()
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
if let chargeId = (threeDSecureStatusResponse?["data"] as? [String: Any])?["charge_id"] as? String,
|
|
722
|
+
!chargeId.isEmpty {
|
|
723
|
+
// Charge already found - do nothing
|
|
724
|
+
return
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
if countdownRemaining > 0 {
|
|
728
|
+
imgViewLoading.isHidden = false
|
|
729
|
+
imgViewPaymentDone.isHidden = true
|
|
730
|
+
if !didTapContinue {
|
|
731
|
+
lblCompleteAuthentication.text = "Tap Continue to proceed with 3D Secure authentication."
|
|
732
|
+
}
|
|
733
|
+
btnDone.isHidden = true
|
|
734
|
+
btnContinue.isHidden = false
|
|
735
|
+
|
|
736
|
+
if apiStatusCheckTimer == nil {
|
|
737
|
+
apiStatusCheckTimer = Timer.scheduledTimer(withTimeInterval: 5.0, repeats: true) { [weak self] _ in
|
|
738
|
+
guard let self = self else { return }
|
|
739
|
+
if self.countdownRemaining > 0 {
|
|
740
|
+
self.checkThreeDSecureStatus(completion: nil)
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
} else {
|
|
745
|
+
imgViewLoading.layer.removeAllAnimations()
|
|
746
|
+
imgViewLoading.isHidden = true
|
|
747
|
+
imgViewPaymentDone.isHidden = false
|
|
748
|
+
imgViewPaymentDone.image = UIImage(systemName: "exclamationmark.circle.fill")
|
|
749
|
+
imgViewPaymentDone.tintColor = .systemRed
|
|
750
|
+
if !didTapContinue {
|
|
751
|
+
lblCompleteAuthentication.text = "Tap Continue to proceed with 3D Secure authentication."
|
|
752
|
+
}
|
|
753
|
+
btnDone.isHidden = false
|
|
754
|
+
viewPaymentDetails.isHidden = false
|
|
755
|
+
btnContinue.isHidden = true
|
|
756
|
+
|
|
757
|
+
apiStatusCheckTimer?.invalidate()
|
|
758
|
+
apiStatusCheckTimer = nil
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
override func viewWillDisappear(_ animated: Bool) {
|
|
763
|
+
super.viewWillDisappear(animated)
|
|
764
|
+
apiStatusCheckTimer?.invalidate()
|
|
765
|
+
apiStatusCheckTimer = nil
|
|
766
|
+
oneMinuteCountdownTimer?.cancel()
|
|
767
|
+
oneMinuteCountdownTimer = nil
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
private func showHourglassGIF() {
|
|
771
|
+
guard let bundlePath = Bundle.easyPayBundle.path(forResource: "hourglass", ofType: "gif"),
|
|
772
|
+
let gifData = try? Data(contentsOf: URL(fileURLWithPath: bundlePath)),
|
|
773
|
+
let source = CGImageSourceCreateWithData(gifData as CFData, nil) else {
|
|
774
|
+
print("⚠️ Could not load hourglass GIF from bundle, falling back to system image")
|
|
775
|
+
imgViewLoading.image = UIImage(systemName: "hourglass")?.withRenderingMode(.alwaysTemplate)
|
|
776
|
+
imgViewLoading.tintColor = UIColor.systemGray
|
|
777
|
+
imgViewLoading.contentMode = .scaleAspectFit
|
|
778
|
+
return
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
var images: [UIImage] = []
|
|
782
|
+
var duration: Double = 0
|
|
783
|
+
|
|
784
|
+
let frameCount = CGImageSourceGetCount(source)
|
|
785
|
+
for i in 0..<frameCount {
|
|
786
|
+
if let cgImage = CGImageSourceCreateImageAtIndex(source, i, nil) {
|
|
787
|
+
let frameDuration = getFrameDuration(from: source, index: i)
|
|
788
|
+
duration += frameDuration
|
|
789
|
+
images.append(UIImage(cgImage: cgImage))
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
if !images.isEmpty {
|
|
794
|
+
let animatedImage = UIImage.animatedImage(with: images, duration: duration)
|
|
795
|
+
imgViewLoading.image = animatedImage
|
|
796
|
+
imgViewLoading.contentMode = .scaleToFill
|
|
797
|
+
} else {
|
|
798
|
+
print("⚠️ Failed to create animated GIF, falling back to system image")
|
|
799
|
+
imgViewLoading.image = UIImage(systemName: "hourglass")?.withRenderingMode(.alwaysTemplate)
|
|
800
|
+
imgViewLoading.tintColor = UIColor.systemGray
|
|
801
|
+
imgViewLoading.contentMode = .scaleAspectFit
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
private func getFrameDuration(from source: CGImageSource, index: Int) -> Double {
|
|
806
|
+
let defaultFrameDuration = 0.1
|
|
807
|
+
|
|
808
|
+
guard let properties = CGImageSourceCopyPropertiesAtIndex(source, index, nil) as? [CFString: Any],
|
|
809
|
+
let gifProperties = properties[kCGImagePropertyGIFDictionary] as? [CFString: Any] else {
|
|
810
|
+
return defaultFrameDuration
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
if let unclampedDelay = gifProperties[kCGImagePropertyGIFUnclampedDelayTime] as? Double, unclampedDelay > 0 {
|
|
814
|
+
return unclampedDelay
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
if let delay = gifProperties[kCGImagePropertyGIFDelayTime] as? Double, delay > 0 {
|
|
818
|
+
return delay
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
return defaultFrameDuration
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
func startRotatingImage() {
|
|
825
|
+
showHourglassGIF()
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
@IBAction func actionBtnContinue(_ sender: UIButton) {
|
|
829
|
+
guard let urlStr = redirectURL, !urlStr.isEmpty else {
|
|
830
|
+
print("Redirect URL is nil or empty")
|
|
831
|
+
return
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
didTapContinue = true
|
|
835
|
+
lblCompleteAuthentication.text = "Waiting for 3DS Authentication..."
|
|
836
|
+
btnContinue.isHidden = true
|
|
837
|
+
|
|
838
|
+
let vc = easymerchantsdk.instantiateViewController(withIdentifier: "PaymentStatusWebViewVC") as! PaymentStatusWebViewVC
|
|
839
|
+
vc.urlString = urlStr
|
|
840
|
+
self.navigationController?.pushViewController(vc, animated: true)
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
private func populateInitialLabels() {
|
|
844
|
+
// Populate labels with initial data from chargeData or defaults
|
|
845
|
+
let refToken = chargeData["ref_token"] as? String ?? "N/A"
|
|
846
|
+
let chargeId = chargeData["charge_id"] as? String ?? "N/A"
|
|
847
|
+
let transactionId = chargeData["transaction_id"] as? String ?? "N/A"
|
|
848
|
+
let subscriptionId = chargeData["subscription_id"] as? String ?? "N/A"
|
|
849
|
+
let createdAt = chargeData["created_at"] as? String ?? ""
|
|
850
|
+
let status = chargeData["status"] as? String ?? "N/A"
|
|
851
|
+
|
|
852
|
+
let formattedDate: String
|
|
853
|
+
if let date = convertToDate(dateString: createdAt) {
|
|
854
|
+
formattedDate = formatDate(date: date)
|
|
855
|
+
} else {
|
|
856
|
+
formattedDate = "N/A"
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
lblChargeId.text = chargeId
|
|
860
|
+
lblTransactionId.text = transactionId
|
|
861
|
+
lblSubscriptionId.text = subscriptionId
|
|
862
|
+
lblDate.text = formattedDate
|
|
863
|
+
let rawAmount = Double(request?.amount ?? 0)
|
|
864
|
+
let amountText = String(format: "$%.2f", rawAmount)
|
|
865
|
+
lblTotal.text = amountText
|
|
866
|
+
lblRefferenceId.text = refToken
|
|
867
|
+
lblStatus.text = status
|
|
868
|
+
lblPaymentMethodValue.text = "Card" // Set to "Card" by default
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
private func startOneMinuteCountdownAndAPICheck() {
|
|
872
|
+
startRotatingImage()
|
|
873
|
+
imgViewPaymentDone.isHidden = true
|
|
874
|
+
imgViewLoading.isHidden = false
|
|
875
|
+
if !didTapContinue {
|
|
876
|
+
lblCompleteAuthentication.text = "Tap Continue to proceed with 3D Secure authentication."
|
|
877
|
+
}
|
|
878
|
+
btnDone.isHidden = true
|
|
879
|
+
btnContinue.isHidden = false
|
|
880
|
+
|
|
881
|
+
apiStatusCheckTimer?.invalidate()
|
|
882
|
+
apiStatusCheckTimer = nil
|
|
883
|
+
apiAttemptCount = 0
|
|
884
|
+
|
|
885
|
+
initialAPIGroup.enter()
|
|
886
|
+
checkThreeDSecureStatus { [weak self] in
|
|
887
|
+
self?.initialAPIGroup.leave()
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
countdownRemaining = 180
|
|
891
|
+
|
|
892
|
+
oneMinuteCountdownTimer = DispatchSource.makeTimerSource(queue: .main)
|
|
893
|
+
oneMinuteCountdownTimer?.schedule(deadline: .now(), repeating: .seconds(1))
|
|
894
|
+
oneMinuteCountdownTimer?.setEventHandler { [weak self] in
|
|
895
|
+
guard let self = self else { return }
|
|
896
|
+
|
|
897
|
+
self.countdownRemaining -= 1
|
|
898
|
+
if self.countdownRemaining <= 0 {
|
|
899
|
+
self.oneMinuteCountdownTimer?.cancel()
|
|
900
|
+
self.oneMinuteCountdownTimer = nil
|
|
901
|
+
|
|
902
|
+
DispatchQueue.main.async {
|
|
903
|
+
self.imgViewLoading.layer.removeAllAnimations()
|
|
904
|
+
self.imgViewLoading.isHidden = true
|
|
905
|
+
self.imgViewPaymentDone.isHidden = false
|
|
906
|
+
self.imgViewPaymentDone.image = UIImage(systemName: "exclamationmark.circle.fill")
|
|
907
|
+
self.imgViewPaymentDone.tintColor = .systemRed
|
|
908
|
+
if !self.didTapContinue {
|
|
909
|
+
self.lblCompleteAuthentication.text = "Tap Continue to proceed with 3D Secure authentication."
|
|
910
|
+
}
|
|
911
|
+
self.btnDone.isHidden = false
|
|
912
|
+
self.viewPaymentDetails.isHidden = false
|
|
913
|
+
self.btnContinue.isHidden = true
|
|
914
|
+
|
|
915
|
+
if let json = self.threeDSecureStatusResponse,
|
|
916
|
+
let data = json["data"] as? [String: Any] {
|
|
917
|
+
let refToken = data["ref_token"] as? String ?? "N/A"
|
|
918
|
+
let chargeId = data["charge_id"] as? String ?? "N/A"
|
|
919
|
+
let transactionId = data["transaction_id"] as? String ?? "N/A"
|
|
920
|
+
let subscriptionId = data["subscription_id"] as? String ?? "N/A"
|
|
921
|
+
let createdAt = data["created_at"] as? String ?? ""
|
|
922
|
+
|
|
923
|
+
let formattedDate: String
|
|
924
|
+
if let date = self.convertToDate(dateString: createdAt) {
|
|
925
|
+
formattedDate = self.formatDate(date: date)
|
|
926
|
+
} else {
|
|
927
|
+
formattedDate = "N/A"
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
self.lblChargeId.text = chargeId
|
|
931
|
+
self.lblTransactionId.text = transactionId
|
|
932
|
+
self.lblSubscriptionId.text = subscriptionId
|
|
933
|
+
self.lblDate.text = formattedDate
|
|
934
|
+
let rawAmount = Double(self.request?.amount ?? 0)
|
|
935
|
+
let amountText = String(format: "$%.2f", rawAmount)
|
|
936
|
+
self.lblTotal.text = amountText
|
|
937
|
+
self.lblRefferenceId.text = refToken
|
|
938
|
+
self.lblPaymentMethodValue.text = "Card"
|
|
939
|
+
}
|
|
940
|
+
// Force cancelled status after 180 seconds if user didn't tap Continue
|
|
941
|
+
self.lblStatus.text = "cancelled"
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
oneMinuteCountdownTimer?.resume()
|
|
946
|
+
|
|
947
|
+
apiStatusCheckTimer = Timer.scheduledTimer(withTimeInterval: 5.0, repeats: true) { [weak self] _ in
|
|
948
|
+
guard let self = self else { return }
|
|
949
|
+
self.checkThreeDSecureStatus(completion: nil)
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
@IBAction func actionBtnDone(_ sender: UIButton) {
|
|
954
|
+
var resultBillingInfo: [String: Any]? = nil
|
|
955
|
+
|
|
956
|
+
if let cardNumber = cardApiParams?["card_number"] as? String, cardNumber.count >= 4 {
|
|
957
|
+
let last4 = String(cardNumber.suffix(4))
|
|
958
|
+
let masked = "****\(last4)"
|
|
959
|
+
chargeData["card_number_last_4"] = masked
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
if let billing = billingInfo, !billing.isEmpty {
|
|
963
|
+
resultBillingInfo = billing
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
var response: [String: Any] = [:]
|
|
967
|
+
|
|
968
|
+
if let threeDS = threeDSecureStatusResponse {
|
|
969
|
+
for (key, value) in threeDS {
|
|
970
|
+
response[key] = value
|
|
971
|
+
}
|
|
972
|
+
} else {
|
|
973
|
+
for (key, value) in chargeData {
|
|
974
|
+
response[key] = value
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
if let billing = resultBillingInfo {
|
|
979
|
+
response["billingInfo"] = billing
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
if let additional = additionalInfo {
|
|
983
|
+
response["additionalInfo"] = additional
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
if let last4 = chargeData["card_number_last_4"] as? String, response["card_number_last_4"] == nil {
|
|
987
|
+
response["card_number_last_4"] = last4
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
let result = SDKResult(type: .success, data: response as NSDictionary)
|
|
991
|
+
|
|
992
|
+
easyPayDelegate?.easyPayController(self.navigationController as! EasyPayViewController, didFinishWith: result)
|
|
993
|
+
|
|
994
|
+
if let easyPayVC = self.navigationController as? EasyPayViewController {
|
|
995
|
+
easyPayVC.dismiss(animated: true, completion: {
|
|
996
|
+
if let delegate = easyPayVC.easyPayDelegate {
|
|
997
|
+
UserStoreSingleton.shared.isLoggedIn = false
|
|
998
|
+
delegate.easyPayController(easyPayVC, didFinishWith: result)
|
|
999
|
+
}
|
|
1000
|
+
})
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
func uiFinishingTouchElements() {
|
|
1005
|
+
if let containerBGcolor = UserStoreSingleton.shared.container_bg_col,
|
|
1006
|
+
let uiColor = UIColor(hex: containerBGcolor) {
|
|
1007
|
+
self.view.backgroundColor = uiColor
|
|
1008
|
+
self.viewMain.backgroundColor = uiColor
|
|
1009
|
+
self.imgViewLoading.backgroundColor = uiColor
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
if let primaryBtnBackGroundColor = UserStoreSingleton.shared.primary_btn_bg_col,
|
|
1013
|
+
let uiColor = UIColor(hex: primaryBtnBackGroundColor) {
|
|
1014
|
+
btnDone.backgroundColor = uiColor
|
|
1015
|
+
// Removed imgViewLoading.image assignment to avoid overriding GIF
|
|
1016
|
+
imgViewLoading.tintColor = uiColor
|
|
1017
|
+
btnContinue.backgroundColor = uiColor
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
if let primaryBtnFontColor = UserStoreSingleton.shared.primary_btn_font_col,
|
|
1021
|
+
let secondaryUIColor = UIColor(hex: primaryBtnFontColor) {
|
|
1022
|
+
btnDone.setTitleColor(secondaryUIColor, for: .normal)
|
|
1023
|
+
btnContinue.setTitleColor(secondaryUIColor, for: .normal)
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
if let primaryFontColor = UserStoreSingleton.shared.primary_font_col,
|
|
1027
|
+
let uiColor = UIColor(hex: primaryFontColor) {
|
|
1028
|
+
lblDateHeading.textColor = uiColor
|
|
1029
|
+
lblTransactionHeading.textColor = uiColor
|
|
1030
|
+
lblSubscriptionHeading.textColor = uiColor
|
|
1031
|
+
lblChargeIdHeading.textColor = uiColor
|
|
1032
|
+
lblReferenceIdHeading.textColor = uiColor
|
|
1033
|
+
lblPaymentMethodHeading.textColor = uiColor
|
|
1034
|
+
lblTotalHeading.textColor = uiColor
|
|
1035
|
+
lblDate.textColor = uiColor
|
|
1036
|
+
lblTransactionId.textColor = uiColor
|
|
1037
|
+
lblSubscriptionId.textColor = uiColor
|
|
1038
|
+
lblChargeId.textColor = uiColor
|
|
1039
|
+
lblTotal.textColor = uiColor
|
|
1040
|
+
lblRefferenceId.textColor = uiColor
|
|
1041
|
+
lblPaymentMethodValue.textColor = uiColor
|
|
1042
|
+
lblStatusHeading.textColor = uiColor
|
|
1043
|
+
lblStatus.textColor = uiColor
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
if let borderRadiusString = UserStoreSingleton.shared.border_radious,
|
|
1047
|
+
let borderRadius = Double(borderRadiusString) {
|
|
1048
|
+
btnDone.layer.cornerRadius = CGFloat(borderRadius)
|
|
1049
|
+
} else {
|
|
1050
|
+
btnDone.layer.cornerRadius = 8
|
|
1051
|
+
}
|
|
1052
|
+
btnDone.layer.masksToBounds = true
|
|
1053
|
+
|
|
1054
|
+
if let primaryFontColor = UserStoreSingleton.shared.primary_font_col,
|
|
1055
|
+
let uiColor = UIColor(hex: primaryFontColor) {
|
|
1056
|
+
lblCompleteAuthentication.textColor = uiColor
|
|
1057
|
+
lblBillingInfoData.textColor = uiColor
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
if let fontSizeString = UserStoreSingleton.shared.fontSize,
|
|
1061
|
+
let fontSizeDouble = Double(fontSizeString) {
|
|
1062
|
+
let fontSize = CGFloat(fontSizeDouble)
|
|
1063
|
+
lblBillingInfoData.font = UIFont.systemFont(ofSize: fontSize, weight: .medium)
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
private func convertToDate(dateString: String) -> Date? {
|
|
1068
|
+
let formatter = DateFormatter()
|
|
1069
|
+
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
|
|
1070
|
+
formatter.timeZone = TimeZone(abbreviation: "UTC")
|
|
1071
|
+
return formatter.date(from: dateString)
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
private func formatDate(date: Date) -> String {
|
|
1075
|
+
let formatter = DateFormatter()
|
|
1076
|
+
formatter.dateFormat = "dd/MM/yyyy, HH:mm:ss"
|
|
1077
|
+
formatter.timeZone = TimeZone.current
|
|
1078
|
+
return formatter.string(from: date)
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
private func checkThreeDSecureStatus(completion: (() -> Void)?) {
|
|
1082
|
+
guard let secureToken = chargeData["secure_token"] as? String else {
|
|
1083
|
+
print("Secure token not found in chargeData")
|
|
1084
|
+
completion?()
|
|
1085
|
+
return
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
apiAttemptCount += 1
|
|
1089
|
+
|
|
1090
|
+
let urlString = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.threeDSecureStatus(secureToken).path()
|
|
1091
|
+
print("Final 3DS status URL: \(urlString)")
|
|
1092
|
+
|
|
1093
|
+
guard let url = URL(string: urlString) else {
|
|
1094
|
+
print("Invalid URL")
|
|
1095
|
+
completion?()
|
|
1096
|
+
return
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
var uRLRequest = URLRequest(url: url)
|
|
1100
|
+
uRLRequest.httpMethod = "GET"
|
|
1101
|
+
uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
1102
|
+
|
|
1103
|
+
let token = UserStoreSingleton.shared.clientToken ?? ""
|
|
1104
|
+
uRLRequest.addValue(token, forHTTPHeaderField: "clientToken")
|
|
1105
|
+
|
|
1106
|
+
let task = URLSession.shared.dataTask(with: uRLRequest) { [weak self] data, response, error in
|
|
1107
|
+
defer { completion?() }
|
|
1108
|
+
|
|
1109
|
+
guard let self = self else { return }
|
|
1110
|
+
|
|
1111
|
+
if let error = error {
|
|
1112
|
+
print("3DS status check error:", error.localizedDescription)
|
|
1113
|
+
return
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
guard let data = data else {
|
|
1117
|
+
print("No data in response")
|
|
1118
|
+
return
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
do {
|
|
1122
|
+
if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] {
|
|
1123
|
+
print("3DS status response:", json)
|
|
1124
|
+
self.threeDSecureStatusResponse = json
|
|
1125
|
+
|
|
1126
|
+
if let dataDict = json["data"] as? [String: Any] {
|
|
1127
|
+
DispatchQueue.main.async {
|
|
1128
|
+
let chargeId = dataDict["charge_id"] as? String ?? "N/A"
|
|
1129
|
+
let transactionId = dataDict["transaction_id"] as? String ?? "N/A"
|
|
1130
|
+
let subscriptionId = dataDict["subscription_id"] as? String ?? "N/A"
|
|
1131
|
+
let createdAt = dataDict["created_at"] as? String ?? ""
|
|
1132
|
+
let referenceId = dataDict["ref_token"] as? String ?? "N/A"
|
|
1133
|
+
let status = dataDict["status"] as? String ?? "N/A"
|
|
1134
|
+
|
|
1135
|
+
let formattedDate: String
|
|
1136
|
+
if let date = self.convertToDate(dateString: createdAt) {
|
|
1137
|
+
formattedDate = self.formatDate(date: date)
|
|
1138
|
+
} else {
|
|
1139
|
+
formattedDate = "N/A"
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
self.lblChargeId.text = chargeId
|
|
1143
|
+
self.lblTransactionId.text = transactionId
|
|
1144
|
+
self.lblSubscriptionId.text = subscriptionId
|
|
1145
|
+
self.lblDate.text = formattedDate
|
|
1146
|
+
let rawAmount = Double(self.request?.amount ?? 0)
|
|
1147
|
+
let amountText = String(format: "$%.2f", rawAmount)
|
|
1148
|
+
self.lblTotal.text = amountText
|
|
1149
|
+
self.lblRefferenceId.text = referenceId
|
|
1150
|
+
self.lblStatus.text = status
|
|
1151
|
+
self.lblPaymentMethodValue.text = "Card" // Ensure "Card" in all cases
|
|
1152
|
+
|
|
1153
|
+
if status.lowercased() == "completed" {
|
|
1154
|
+
// Success case
|
|
1155
|
+
self.imgViewPaymentDone.image = UIImage(named: "payment_done_icon", in: .easyPayBundle, compatibleWith: nil)
|
|
1156
|
+
self.apiStatusCheckTimer?.invalidate()
|
|
1157
|
+
self.apiStatusCheckTimer = nil
|
|
1158
|
+
self.oneMinuteCountdownTimer?.cancel()
|
|
1159
|
+
self.oneMinuteCountdownTimer = nil
|
|
1160
|
+
|
|
1161
|
+
self.imgViewLoading.layer.removeAllAnimations()
|
|
1162
|
+
self.imgViewLoading.isHidden = true
|
|
1163
|
+
self.imgViewPaymentDone.isHidden = false
|
|
1164
|
+
self.lblCompleteAuthentication.text = "Authentication Completed"
|
|
1165
|
+
self.btnDone.isHidden = false
|
|
1166
|
+
self.viewPaymentDetails.isHidden = false
|
|
1167
|
+
self.btnContinue.isHidden = true
|
|
1168
|
+
} else if status.lowercased() == "failed" {
|
|
1169
|
+
// Failure case
|
|
1170
|
+
self.imgViewPaymentDone.image = UIImage(named: "payment_error_icon", in: .easyPayBundle, compatibleWith: nil)
|
|
1171
|
+
self.imgViewPaymentDone.tintColor = .systemRed
|
|
1172
|
+
self.apiStatusCheckTimer?.invalidate()
|
|
1173
|
+
self.apiStatusCheckTimer = nil
|
|
1174
|
+
self.oneMinuteCountdownTimer?.cancel()
|
|
1175
|
+
self.oneMinuteCountdownTimer = nil
|
|
1176
|
+
|
|
1177
|
+
self.imgViewLoading.layer.removeAllAnimations()
|
|
1178
|
+
self.imgViewLoading.isHidden = true
|
|
1179
|
+
self.imgViewPaymentDone.isHidden = false
|
|
1180
|
+
self.lblCompleteAuthentication.text = "Transaction Failed.!"
|
|
1181
|
+
self.btnDone.isHidden = false
|
|
1182
|
+
self.viewPaymentDetails.isHidden = false
|
|
1183
|
+
self.btnContinue.isHidden = true
|
|
1184
|
+
} else {
|
|
1185
|
+
// Non-terminal statuses
|
|
1186
|
+
if self.didTapContinue {
|
|
1187
|
+
// User returned from WebView: after max two attempts, finalize with whatever status we have
|
|
1188
|
+
if self.apiAttemptCount >= 2 {
|
|
1189
|
+
self.apiStatusCheckTimer?.invalidate()
|
|
1190
|
+
self.apiStatusCheckTimer = nil
|
|
1191
|
+
self.oneMinuteCountdownTimer?.cancel()
|
|
1192
|
+
self.oneMinuteCountdownTimer = nil
|
|
1193
|
+
|
|
1194
|
+
self.imgViewLoading.layer.removeAllAnimations()
|
|
1195
|
+
self.imgViewLoading.isHidden = true
|
|
1196
|
+
self.imgViewPaymentDone.isHidden = false
|
|
1197
|
+
self.imgViewPaymentDone.image = UIImage(systemName: "exclamationmark.circle.fill")
|
|
1198
|
+
self.lblCompleteAuthentication.text = "Transaction cancelled.!"
|
|
1199
|
+
self.imgViewPaymentDone.tintColor = .systemRed
|
|
1200
|
+
self.btnDone.isHidden = false
|
|
1201
|
+
self.viewPaymentDetails.isHidden = false
|
|
1202
|
+
self.btnContinue.isHidden = true
|
|
1203
|
+
// Keep lblStatus as received
|
|
1204
|
+
}
|
|
1205
|
+
} else {
|
|
1206
|
+
// Initial waiting period: do not auto-cancel or finalize before 180s
|
|
1207
|
+
// Keep loading and allow polling to continue
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
} catch {
|
|
1214
|
+
print("Error parsing JSON:", error.localizedDescription)
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
task.resume()
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
|
|
1222
|
+
|
|
1223
|
+
|
|
1224
|
+
|
|
1225
|
+
|
|
1226
|
+
|
|
1227
|
+
|
|
1228
|
+
|
|
1229
|
+
|
|
1230
|
+
|
|
1231
|
+
|
|
1232
|
+
|
|
1233
|
+
|
|
1234
|
+
|
|
1235
|
+
|
|
1236
|
+
|
|
1237
|
+
|
|
1238
|
+
|
|
1239
|
+
|
|
1240
|
+
|
|
1241
|
+
|
|
1242
|
+
|
|
1243
|
+
|
|
1244
|
+
|
|
1245
|
+
|
|
1246
|
+
|
|
1247
|
+
|
|
1248
|
+
|
|
1249
|
+
|
|
1250
|
+
|
|
1251
|
+
|
|
1252
|
+
|
|
1253
|
+
|
|
1254
|
+
|