@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.
- package/README.md +1 -1
- package/ios/Pods/UserDefaults/UserStoreSingleton.swift +304 -0
- package/ios/Pods/ViewControllers/AdditionalInfoVC.swift +2636 -0
- package/ios/Pods/ViewControllers/BaseVC.swift +141 -0
- package/ios/Pods/ViewControllers/BillingInfoVC/BillingInfoVC.swift +3347 -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/CountryListVC.swift +435 -0
- package/ios/Pods/ViewControllers/CustomOverlay.swift +199 -0
- package/ios/Pods/ViewControllers/EmailVerificationVC.swift +307 -0
- package/ios/Pods/ViewControllers/GrailPayVC.swift +244 -0
- package/ios/Pods/ViewControllers/OTPVerificationVC.swift +2066 -0
- package/ios/Pods/ViewControllers/PaymentDoneVC.swift +272 -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 +11025 -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 +81 -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 +629 -0
- package/ios/easymerchantsdk.podspec +1 -1
- 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
|
+
|