@jimrising/easymerchantsdk-react-native 2.6.0 → 2.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/build.gradle +3 -2
- package/ios/ApiManager/APIHeaders.swift +6 -1
- package/ios/Models/Request.swift +15 -0
- package/ios/Pods/UserDefaults/UserStoreSingleton.swift +16 -0
- package/ios/Pods/ViewControllers/AdditionalInfoVC.swift +13 -3
- package/ios/Pods/ViewControllers/BillingInfoVC/BillingInfoVC.swift +3 -3
- package/ios/Pods/ViewControllers/EmailVerificationVC.swift +1 -0
- package/ios/Pods/ViewControllers/OTPVerificationVC.swift +3 -3
- package/ios/Pods/ViewControllers/PaymentDoneVC.swift +30 -8
- package/ios/Pods/ViewControllers/PaymentInformation/PaymentInfoVC.swift +9 -9
- package/ios/Pods/ViewControllers/ThreeDSecurePaymentDoneVC.swift +116 -88
- package/ios/Resources/Assets.xcassets/lock_3ds.imageset/Contents.json +16 -0
- package/ios/Resources/Assets.xcassets/lock_3ds.imageset/lock.svg +4 -0
- package/ios/easymerchantsdk.podspec +1 -1
- package/package.json +1 -1
package/android/build.gradle
CHANGED
|
@@ -7,7 +7,8 @@ if (configFile.exists()) {
|
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
buildscript {
|
|
10
|
-
|
|
10
|
+
// Align with expeditermobile (2.0.21) to avoid Kotlin metadata mismatch
|
|
11
|
+
ext.kotlinVersion = "2.0.21"
|
|
11
12
|
|
|
12
13
|
repositories {
|
|
13
14
|
google()
|
|
@@ -96,7 +97,7 @@ dependencies {
|
|
|
96
97
|
implementation 'com.google.android.material:material:1.13.0'
|
|
97
98
|
|
|
98
99
|
// Third-party libs
|
|
99
|
-
implementation 'com.app:paysdk:1.7.
|
|
100
|
+
implementation 'com.app:paysdk:1.7.9'
|
|
100
101
|
implementation 'com.hbb20:ccp:2.7.3'
|
|
101
102
|
implementation 'com.github.bumptech.glide:glide:5.0.4'
|
|
102
103
|
implementation 'com.github.androidmads:QRGenerator:1.0.5'
|
|
@@ -8,8 +8,13 @@
|
|
|
8
8
|
import Foundation
|
|
9
9
|
|
|
10
10
|
struct APIHeaders {
|
|
11
|
+
static let userAgent = "ESAdvantage"
|
|
12
|
+
|
|
11
13
|
static func commonHeaders(contentType: String = "application/json") -> [String: String] {
|
|
12
|
-
return [
|
|
14
|
+
return [
|
|
15
|
+
"Content-Type": contentType,
|
|
16
|
+
"User-Agent": userAgent
|
|
17
|
+
]
|
|
13
18
|
}
|
|
14
19
|
|
|
15
20
|
static func clientTokenHeader() -> [String: String] {
|
package/ios/Models/Request.swift
CHANGED
|
@@ -516,6 +516,7 @@ public final class Request: NSObject {
|
|
|
516
516
|
var request = URLRequest(url: serviceURL)
|
|
517
517
|
request.httpMethod = "POST"
|
|
518
518
|
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
519
|
+
request.addValue(APIHeaders.userAgent, forHTTPHeaderField: "User-Agent")
|
|
519
520
|
request.addValue(EnvironmentConfig.apiKey ?? "", forHTTPHeaderField: "X-Api-Key")
|
|
520
521
|
request.addValue(EnvironmentConfig.apiSecret ?? "", forHTTPHeaderField: "X-Api-Secret")
|
|
521
522
|
|
|
@@ -671,6 +672,7 @@ public final class Request: NSObject {
|
|
|
671
672
|
var request = URLRequest(url: serviceURL)
|
|
672
673
|
request.httpMethod = "POST"
|
|
673
674
|
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
675
|
+
request.addValue(APIHeaders.userAgent, forHTTPHeaderField: "User-Agent")
|
|
674
676
|
|
|
675
677
|
let token = clientToken ?? UserStoreSingleton.shared.clientToken ?? ""
|
|
676
678
|
if token.isEmpty {
|
|
@@ -774,6 +776,11 @@ public final class Request: NSObject {
|
|
|
774
776
|
UserStoreSingleton.shared.amount = "\(paymentAmount)"
|
|
775
777
|
}
|
|
776
778
|
|
|
779
|
+
// Set secureAuthentication from hostedcheckout three_d_secure
|
|
780
|
+
if let threeDSecure = dataObject["three_d_secure"] as? Bool {
|
|
781
|
+
UserStoreSingleton.shared.secureAuthentication = threeDSecure
|
|
782
|
+
}
|
|
783
|
+
|
|
777
784
|
// Save recurring details
|
|
778
785
|
if let recurringDetails = dataObject["recurring_details"] as? [String: Any] {
|
|
779
786
|
if let allowedCycles = recurringDetails["allowed_cycles"] {
|
|
@@ -932,6 +939,7 @@ public final class Request: NSObject {
|
|
|
932
939
|
var uRLRequest = URLRequest(url: url)
|
|
933
940
|
uRLRequest.httpMethod = "GET"
|
|
934
941
|
uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
942
|
+
uRLRequest.addValue(APIHeaders.userAgent, forHTTPHeaderField: "User-Agent")
|
|
935
943
|
|
|
936
944
|
// let tokenn = UserStoreSingleton.shared.customerToken
|
|
937
945
|
// uRLRequest.addValue(tokenn ?? "", forHTTPHeaderField: "customer-token")
|
|
@@ -972,6 +980,13 @@ public final class Request: NSObject {
|
|
|
972
980
|
|
|
973
981
|
}
|
|
974
982
|
|
|
983
|
+
extension Request {
|
|
984
|
+
/// Effective 3DS flag: from hosted checkout (three_d_secure) when set, otherwise from init config.
|
|
985
|
+
var effectiveSecureAuthentication: Bool {
|
|
986
|
+
(UserStoreSingleton.shared.secureAuthentication ?? secureAuthentication) == true
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
|
|
975
990
|
extension DateFormatter {
|
|
976
991
|
static let recurringDateFormatter: DateFormatter = {
|
|
977
992
|
let formatter = DateFormatter()
|
|
@@ -212,6 +212,21 @@ class UserStoreSingleton: NSObject {
|
|
|
212
212
|
}
|
|
213
213
|
}
|
|
214
214
|
|
|
215
|
+
/// 3DS enabled flag from hosted checkout response (three_d_secure). When set, overrides request.secureAuthentication.
|
|
216
|
+
var secureAuthentication: Bool? {
|
|
217
|
+
get {
|
|
218
|
+
guard UserDefaults.standard.object(forKey: "secure_authentication") != nil else { return nil }
|
|
219
|
+
return UserDefaults.standard.bool(forKey: "secure_authentication")
|
|
220
|
+
}
|
|
221
|
+
set {
|
|
222
|
+
if let v = newValue {
|
|
223
|
+
UserDefaults.standard.set(v, forKey: "secure_authentication")
|
|
224
|
+
} else {
|
|
225
|
+
UserDefaults.standard.removeObject(forKey: "secure_authentication")
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
215
230
|
//apperance_settings
|
|
216
231
|
var body_bg_col : String? {
|
|
217
232
|
get {
|
|
@@ -338,6 +353,7 @@ class UserStoreSingleton: NSObject {
|
|
|
338
353
|
"payment_methods",
|
|
339
354
|
"bank_widget_key",
|
|
340
355
|
"vendor_id",
|
|
356
|
+
"secure_authentication",
|
|
341
357
|
// "amount"
|
|
342
358
|
// "allowCycles",
|
|
343
359
|
// "interval",
|
|
@@ -318,7 +318,7 @@ class AdditionalInfoVC: BaseVC {
|
|
|
318
318
|
if UserStoreSingleton.shared.isLoggedIn == true {
|
|
319
319
|
switch selectedPaymentMethod {
|
|
320
320
|
case "Card":
|
|
321
|
-
if request.
|
|
321
|
+
if request.effectiveSecureAuthentication {
|
|
322
322
|
threeDSecurePaymentApi()
|
|
323
323
|
} else {
|
|
324
324
|
paymentIntentApi()
|
|
@@ -415,7 +415,7 @@ class AdditionalInfoVC: BaseVC {
|
|
|
415
415
|
}
|
|
416
416
|
return
|
|
417
417
|
}
|
|
418
|
-
if request.
|
|
418
|
+
if request.effectiveSecureAuthentication {
|
|
419
419
|
threeDSecurePaymentAddNewCardApi(customerId: UserStoreSingleton.shared.customerId)
|
|
420
420
|
} else {
|
|
421
421
|
paymentIntentAddNewCardApi(customerId: UserStoreSingleton.shared.customerId)
|
|
@@ -423,7 +423,7 @@ class AdditionalInfoVC: BaseVC {
|
|
|
423
423
|
}
|
|
424
424
|
}
|
|
425
425
|
else {
|
|
426
|
-
if request.
|
|
426
|
+
if request.effectiveSecureAuthentication {
|
|
427
427
|
threeDSecurePaymentApi()
|
|
428
428
|
} else {
|
|
429
429
|
paymentIntentApi()
|
|
@@ -477,6 +477,7 @@ class AdditionalInfoVC: BaseVC {
|
|
|
477
477
|
var urlRequest = URLRequest(url: serviceURL)
|
|
478
478
|
urlRequest.httpMethod = "POST"
|
|
479
479
|
urlRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
480
|
+
urlRequest.addValue(APIHeaders.userAgent, forHTTPHeaderField: "User-Agent")
|
|
480
481
|
|
|
481
482
|
let token = UserStoreSingleton.shared.clientToken
|
|
482
483
|
urlRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
|
|
@@ -718,6 +719,7 @@ class AdditionalInfoVC: BaseVC {
|
|
|
718
719
|
var uRLRequest = URLRequest(url: serviceURL)
|
|
719
720
|
uRLRequest.httpMethod = "POST"
|
|
720
721
|
uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
722
|
+
uRLRequest.addValue(APIHeaders.userAgent, forHTTPHeaderField: "User-Agent")
|
|
721
723
|
|
|
722
724
|
let token = UserStoreSingleton.shared.clientToken
|
|
723
725
|
uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
|
|
@@ -973,6 +975,7 @@ class AdditionalInfoVC: BaseVC {
|
|
|
973
975
|
var uRLRequest = URLRequest(url: serviceURL)
|
|
974
976
|
uRLRequest.httpMethod = "POST"
|
|
975
977
|
uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
978
|
+
uRLRequest.addValue(APIHeaders.userAgent, forHTTPHeaderField: "User-Agent")
|
|
976
979
|
|
|
977
980
|
let token = UserStoreSingleton.shared.clientToken
|
|
978
981
|
uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
|
|
@@ -1221,6 +1224,7 @@ class AdditionalInfoVC: BaseVC {
|
|
|
1221
1224
|
var uRLRequest = URLRequest(url: serviceURL)
|
|
1222
1225
|
uRLRequest.httpMethod = "POST"
|
|
1223
1226
|
uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
1227
|
+
uRLRequest.addValue(APIHeaders.userAgent, forHTTPHeaderField: "User-Agent")
|
|
1224
1228
|
|
|
1225
1229
|
let token = UserStoreSingleton.shared.clientToken
|
|
1226
1230
|
uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
|
|
@@ -1456,6 +1460,7 @@ class AdditionalInfoVC: BaseVC {
|
|
|
1456
1460
|
var uRLRequest = URLRequest(url: serviceURL)
|
|
1457
1461
|
uRLRequest.httpMethod = "POST"
|
|
1458
1462
|
uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
1463
|
+
uRLRequest.addValue(APIHeaders.userAgent, forHTTPHeaderField: "User-Agent")
|
|
1459
1464
|
|
|
1460
1465
|
let token = UserStoreSingleton.shared.clientToken
|
|
1461
1466
|
uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
|
|
@@ -1695,6 +1700,7 @@ class AdditionalInfoVC: BaseVC {
|
|
|
1695
1700
|
var uRLRequest = URLRequest(url: serviceURL)
|
|
1696
1701
|
uRLRequest.httpMethod = "POST"
|
|
1697
1702
|
uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
1703
|
+
uRLRequest.addValue(APIHeaders.userAgent, forHTTPHeaderField: "User-Agent")
|
|
1698
1704
|
|
|
1699
1705
|
let token = UserStoreSingleton.shared.clientToken
|
|
1700
1706
|
uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
|
|
@@ -1944,6 +1950,7 @@ class AdditionalInfoVC: BaseVC {
|
|
|
1944
1950
|
var uRLRequest = URLRequest(url: serviceURL)
|
|
1945
1951
|
uRLRequest.httpMethod = "POST"
|
|
1946
1952
|
uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
1953
|
+
uRLRequest.addValue(APIHeaders.userAgent, forHTTPHeaderField: "User-Agent")
|
|
1947
1954
|
|
|
1948
1955
|
let token = UserStoreSingleton.shared.clientToken
|
|
1949
1956
|
uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
|
|
@@ -2187,6 +2194,7 @@ class AdditionalInfoVC: BaseVC {
|
|
|
2187
2194
|
var uRLRequest = URLRequest(url: serviceURL)
|
|
2188
2195
|
uRLRequest.httpMethod = "POST"
|
|
2189
2196
|
uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
2197
|
+
uRLRequest.addValue(APIHeaders.userAgent, forHTTPHeaderField: "User-Agent")
|
|
2190
2198
|
|
|
2191
2199
|
let token = UserStoreSingleton.shared.clientToken
|
|
2192
2200
|
uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
|
|
@@ -2413,6 +2421,7 @@ class AdditionalInfoVC: BaseVC {
|
|
|
2413
2421
|
var uRLRequest = URLRequest(url: serviceURL)
|
|
2414
2422
|
uRLRequest.httpMethod = "POST"
|
|
2415
2423
|
uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
2424
|
+
uRLRequest.addValue(APIHeaders.userAgent, forHTTPHeaderField: "User-Agent")
|
|
2416
2425
|
|
|
2417
2426
|
let token = UserStoreSingleton.shared.clientToken
|
|
2418
2427
|
uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
|
|
@@ -2657,6 +2666,7 @@ class AdditionalInfoVC: BaseVC {
|
|
|
2657
2666
|
var uRLRequest = URLRequest(url: serviceURL)
|
|
2658
2667
|
uRLRequest.httpMethod = "POST"
|
|
2659
2668
|
uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
2669
|
+
uRLRequest.addValue(APIHeaders.userAgent, forHTTPHeaderField: "User-Agent")
|
|
2660
2670
|
|
|
2661
2671
|
let token = UserStoreSingleton.shared.clientToken
|
|
2662
2672
|
uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
|
|
@@ -754,7 +754,7 @@ class BillingInfoVC: BaseVC {
|
|
|
754
754
|
}
|
|
755
755
|
else if !isAdditionalVisible && isSavedForFuture {
|
|
756
756
|
if UserStoreSingleton.shared.isLoggedIn == true {
|
|
757
|
-
if request.
|
|
757
|
+
if request.effectiveSecureAuthentication {
|
|
758
758
|
threeDSecurePaymentApi()
|
|
759
759
|
} else {
|
|
760
760
|
paymentIntentApi()
|
|
@@ -787,7 +787,7 @@ class BillingInfoVC: BaseVC {
|
|
|
787
787
|
}
|
|
788
788
|
else if !isAdditionalVisible && isSavedNewCard {
|
|
789
789
|
if isFrom == "AddNewCard" {
|
|
790
|
-
if request.
|
|
790
|
+
if request.effectiveSecureAuthentication {
|
|
791
791
|
threeDSecurePaymentAddNewCardApi(customerId: UserStoreSingleton.shared.customerId)
|
|
792
792
|
}
|
|
793
793
|
else {
|
|
@@ -797,7 +797,7 @@ class BillingInfoVC: BaseVC {
|
|
|
797
797
|
}
|
|
798
798
|
else {
|
|
799
799
|
// ✅ Skip to Payment directly
|
|
800
|
-
if request.
|
|
800
|
+
if request.effectiveSecureAuthentication {
|
|
801
801
|
threeDSecurePaymentApi()
|
|
802
802
|
} else {
|
|
803
803
|
paymentIntentApi()
|
|
@@ -169,6 +169,7 @@ class EmailVerificationVC: BaseVC {
|
|
|
169
169
|
var request = URLRequest(url: serviceURL)
|
|
170
170
|
request.httpMethod = "POST"
|
|
171
171
|
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
172
|
+
request.addValue(APIHeaders.userAgent, forHTTPHeaderField: "User-Agent")
|
|
172
173
|
|
|
173
174
|
let token = UserStoreSingleton.shared.clientToken
|
|
174
175
|
request.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
|
|
@@ -402,7 +402,7 @@ class OTPVerificationVC: BaseVC {
|
|
|
402
402
|
let innerData = dataSection["data"] as? [String: Any],
|
|
403
403
|
let customerId = innerData["customer_id"] as? String {
|
|
404
404
|
if self.billingInfoData == nil {
|
|
405
|
-
if self.request.
|
|
405
|
+
if self.request.effectiveSecureAuthentication {
|
|
406
406
|
self.threeDSecurePaymentApi(customerId: customerId)
|
|
407
407
|
}
|
|
408
408
|
else {
|
|
@@ -412,7 +412,7 @@ class OTPVerificationVC: BaseVC {
|
|
|
412
412
|
else {
|
|
413
413
|
// self.paymentIntentApi(customerId: customerId)
|
|
414
414
|
if let request = self.request {
|
|
415
|
-
if request.
|
|
415
|
+
if request.effectiveSecureAuthentication {
|
|
416
416
|
self.threeDSecurePaymentSavedCardApi(customerId: customerId)
|
|
417
417
|
} else {
|
|
418
418
|
self.paymentIntentApi(customerId: customerId)
|
|
@@ -421,7 +421,7 @@ class OTPVerificationVC: BaseVC {
|
|
|
421
421
|
}
|
|
422
422
|
} else {
|
|
423
423
|
if let request = self.request {
|
|
424
|
-
if request.
|
|
424
|
+
if request.effectiveSecureAuthentication {
|
|
425
425
|
self.threeDSecurePaymentSavedCardApi(customerId: nil)
|
|
426
426
|
}
|
|
427
427
|
else {
|
|
@@ -203,14 +203,36 @@ class PaymentDoneVC: UIViewController {
|
|
|
203
203
|
}
|
|
204
204
|
|
|
205
205
|
func showDateAndTime() {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
206
|
+
let createdAt = chargeData?["created_at"] as? String
|
|
207
|
+
?? (chargeData?["data"] as? [String: Any])?["created_at"] as? String
|
|
208
|
+
if let createdAt = createdAt, !createdAt.isEmpty, let formatted = formatApiDateTime(createdAt) {
|
|
209
|
+
lblDate.text = formatted
|
|
210
|
+
} else {
|
|
211
|
+
let dateFormatter = DateFormatter()
|
|
212
|
+
dateFormatter.dateFormat = "dd/MM/yyyy, HH:mm:ss"
|
|
213
|
+
lblDate.text = dateFormatter.string(from: Date())
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
private func formatApiDateTime(_ apiDateString: String) -> String? {
|
|
218
|
+
let displayFormat = "dd/MM/yyyy, HH:mm:ss"
|
|
219
|
+
let formatters = [
|
|
220
|
+
"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'",
|
|
221
|
+
"yyyy-MM-dd'T'HH:mm:ss'Z'",
|
|
222
|
+
"yyyy-MM-dd HH:mm:ss"
|
|
223
|
+
]
|
|
224
|
+
for fmt in formatters {
|
|
225
|
+
let parser = DateFormatter()
|
|
226
|
+
parser.dateFormat = fmt
|
|
227
|
+
parser.timeZone = TimeZone(identifier: "UTC")
|
|
228
|
+
if let date = parser.date(from: apiDateString) {
|
|
229
|
+
let out = DateFormatter()
|
|
230
|
+
out.dateFormat = displayFormat
|
|
231
|
+
out.timeZone = TimeZone(identifier: "UTC")
|
|
232
|
+
return out.string(from: date)
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return nil
|
|
214
236
|
}
|
|
215
237
|
|
|
216
238
|
// @IBAction func actionBtnDone(_ sender: UIButton) {
|
|
@@ -2993,7 +2993,7 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
|
|
|
2993
2993
|
NSLog("%@", "[PayNowNewCard] LOGGED IN: showBilling=\(showBilling), showAdditional=\(showAdditional)")
|
|
2994
2994
|
if !showBilling && !showAdditional {
|
|
2995
2995
|
NSLog("%@", "[PayNowNewCard] LOGGED IN: no billing → calling payment API directly")
|
|
2996
|
-
if self.request.
|
|
2996
|
+
if self.request.effectiveSecureAuthentication {
|
|
2997
2997
|
self.threeDSecurePaymentNewCardApi(customerId: UserStoreSingleton.shared.customerId ?? "")
|
|
2998
2998
|
} else {
|
|
2999
2999
|
self.paymentIntentAddNewCardApi(customerId: UserStoreSingleton.shared.customerId)
|
|
@@ -3060,7 +3060,7 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
|
|
|
3060
3060
|
}
|
|
3061
3061
|
else {
|
|
3062
3062
|
NSLog("%@", "[PayNowNewCard] LOGGED IN: no billingInfoData → calling payment API")
|
|
3063
|
-
if request.
|
|
3063
|
+
if request.effectiveSecureAuthentication {
|
|
3064
3064
|
threeDSecurePaymentNewCardApi(customerId: UserStoreSingleton.shared.customerId ?? "")
|
|
3065
3065
|
}
|
|
3066
3066
|
else {
|
|
@@ -3162,7 +3162,7 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
|
|
|
3162
3162
|
}
|
|
3163
3163
|
} else {
|
|
3164
3164
|
NSLog("%@", "[PayNowNewCard] NOT LOGGED IN: saveCard not checked → calling payment API (bypassing OTP)")
|
|
3165
|
-
if self.request.
|
|
3165
|
+
if self.request.effectiveSecureAuthentication {
|
|
3166
3166
|
self.threeDSecurePaymentNewCardApi(customerId: UserStoreSingleton.shared.customerId ?? "")
|
|
3167
3167
|
} else {
|
|
3168
3168
|
self.paymentIntentFromAddNewCardApi(customerId: UserStoreSingleton.shared.customerId)
|
|
@@ -3217,7 +3217,7 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
|
|
|
3217
3217
|
}
|
|
3218
3218
|
} else {
|
|
3219
3219
|
NSLog("%@", "[PayNowNewCard] NOT LOGGED IN: no billingInfoData → calling payment API (bypassing OTP)")
|
|
3220
|
-
if request.
|
|
3220
|
+
if request.effectiveSecureAuthentication {
|
|
3221
3221
|
threeDSecurePaymentNewCardApi(customerId: UserStoreSingleton.shared.customerId ?? "")
|
|
3222
3222
|
} else {
|
|
3223
3223
|
paymentIntentFromAddNewCardApi(customerId: UserStoreSingleton.shared.customerId)
|
|
@@ -5201,7 +5201,7 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
|
|
|
5201
5201
|
if !showBilling && !showAdditional {
|
|
5202
5202
|
if saveCardChecked {
|
|
5203
5203
|
if UserStoreSingleton.shared.isLoggedIn == true {
|
|
5204
|
-
if self.request.
|
|
5204
|
+
if self.request.effectiveSecureAuthentication {
|
|
5205
5205
|
self.threeDSecurePaymentApi()
|
|
5206
5206
|
} else {
|
|
5207
5207
|
self.paymentIntentCardApi()
|
|
@@ -5211,7 +5211,7 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
|
|
|
5211
5211
|
self.navigateCardDataToEmailVerificationVC()
|
|
5212
5212
|
}
|
|
5213
5213
|
} else {
|
|
5214
|
-
if self.request.
|
|
5214
|
+
if self.request.effectiveSecureAuthentication {
|
|
5215
5215
|
self.threeDSecurePaymentApi()
|
|
5216
5216
|
} else {
|
|
5217
5217
|
self.paymentIntentCardApi()
|
|
@@ -5508,7 +5508,7 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
|
|
|
5508
5508
|
// Navigate to EmailVerificationVC if isSavedForFuture is true, else call paymentIntentApi
|
|
5509
5509
|
if isSavedForFuture {
|
|
5510
5510
|
if UserStoreSingleton.shared.isLoggedIn == true {
|
|
5511
|
-
if request.
|
|
5511
|
+
if request.effectiveSecureAuthentication {
|
|
5512
5512
|
threeDSecurePaymentApi()
|
|
5513
5513
|
} else {
|
|
5514
5514
|
paymentIntentApi()
|
|
@@ -5533,7 +5533,7 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
|
|
|
5533
5533
|
}
|
|
5534
5534
|
}
|
|
5535
5535
|
} else {
|
|
5536
|
-
if request.
|
|
5536
|
+
if request.effectiveSecureAuthentication {
|
|
5537
5537
|
threeDSecurePaymentApi()
|
|
5538
5538
|
}
|
|
5539
5539
|
else {
|
|
@@ -6616,7 +6616,7 @@ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
|
|
|
6616
6616
|
self.OTPView.isHidden = true
|
|
6617
6617
|
// After verify_otp: run charge so card is saved (same pattern as OTPVerificationVC → paymentIntentWithSavedCardApi / paymentIntentApi)
|
|
6618
6618
|
if self.isSavedNewCardForFuture {
|
|
6619
|
-
if self.request.
|
|
6619
|
+
if self.request.effectiveSecureAuthentication {
|
|
6620
6620
|
self.threeDSecurePaymentNewCardApi(customerId: customerId)
|
|
6621
6621
|
} else {
|
|
6622
6622
|
self.paymentIntentAddNewCardApi(customerId: customerId)
|
|
@@ -669,6 +669,9 @@ class ThreeDSecurePaymentDoneVC: BaseVC {
|
|
|
669
669
|
|
|
670
670
|
var didTapContinue = false
|
|
671
671
|
|
|
672
|
+
/// Static date for 3DS details (no live timer); set once from first API response.
|
|
673
|
+
private var displayed3dsDate: String?
|
|
674
|
+
|
|
672
675
|
override func viewDidLoad() {
|
|
673
676
|
super.viewDidLoad()
|
|
674
677
|
uiFinishingTouchElements()
|
|
@@ -758,60 +761,17 @@ class ThreeDSecurePaymentDoneVC: BaseVC {
|
|
|
758
761
|
}
|
|
759
762
|
}
|
|
760
763
|
|
|
761
|
-
private func
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
let source = CGImageSourceCreateWithData(gifData as CFData, nil) else {
|
|
765
|
-
imgViewLoading.image = UIImage(systemName: "hourglass")?.withRenderingMode(.alwaysTemplate)
|
|
766
|
-
imgViewLoading.tintColor = UIColor.systemGray
|
|
767
|
-
imgViewLoading.contentMode = .scaleAspectFit
|
|
768
|
-
return
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
var images: [UIImage] = []
|
|
772
|
-
var duration: Double = 0
|
|
773
|
-
|
|
774
|
-
let frameCount = CGImageSourceGetCount(source)
|
|
775
|
-
for i in 0..<frameCount {
|
|
776
|
-
if let cgImage = CGImageSourceCreateImageAtIndex(source, i, nil) {
|
|
777
|
-
let frameDuration = getFrameDuration(from: source, index: i)
|
|
778
|
-
duration += frameDuration
|
|
779
|
-
images.append(UIImage(cgImage: cgImage))
|
|
780
|
-
}
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
if !images.isEmpty {
|
|
784
|
-
let animatedImage = UIImage.animatedImage(with: images, duration: duration)
|
|
785
|
-
imgViewLoading.image = animatedImage
|
|
786
|
-
imgViewLoading.contentMode = .scaleToFill
|
|
764
|
+
private func showLockIcon() {
|
|
765
|
+
if let lockImage = UIImage(named: "lock_3ds", in: .easyPayBundle, compatibleWith: nil) {
|
|
766
|
+
imgViewLoading.image = lockImage.withRenderingMode(.alwaysTemplate)
|
|
787
767
|
} else {
|
|
788
|
-
imgViewLoading.image = UIImage(systemName: "
|
|
789
|
-
imgViewLoading.tintColor = UIColor.systemGray
|
|
790
|
-
imgViewLoading.contentMode = .scaleAspectFit
|
|
768
|
+
imgViewLoading.image = UIImage(systemName: "lock.fill")?.withRenderingMode(.alwaysTemplate)
|
|
791
769
|
}
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
private func getFrameDuration(from source: CGImageSource, index: Int) -> Double {
|
|
795
|
-
let defaultFrameDuration = 0.1
|
|
796
|
-
|
|
797
|
-
guard let properties = CGImageSourceCopyPropertiesAtIndex(source, index, nil) as? [CFString: Any],
|
|
798
|
-
let gifProperties = properties[kCGImagePropertyGIFDictionary] as? [CFString: Any] else {
|
|
799
|
-
return defaultFrameDuration
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
if let unclampedDelay = gifProperties[kCGImagePropertyGIFUnclampedDelayTime] as? Double, unclampedDelay > 0 {
|
|
803
|
-
return unclampedDelay
|
|
804
|
-
}
|
|
805
|
-
|
|
806
|
-
if let delay = gifProperties[kCGImagePropertyGIFDelayTime] as? Double, delay > 0 {
|
|
807
|
-
return delay
|
|
808
|
-
}
|
|
809
|
-
|
|
810
|
-
return defaultFrameDuration
|
|
770
|
+
imgViewLoading.contentMode = .scaleAspectFit
|
|
811
771
|
}
|
|
812
772
|
|
|
813
773
|
func startRotatingImage() {
|
|
814
|
-
|
|
774
|
+
showLockIcon()
|
|
815
775
|
}
|
|
816
776
|
|
|
817
777
|
@IBAction func actionBtnContinue(_ sender: UIButton) {
|
|
@@ -828,32 +788,64 @@ class ThreeDSecurePaymentDoneVC: BaseVC {
|
|
|
828
788
|
self.navigationController?.pushViewController(vc, animated: true)
|
|
829
789
|
}
|
|
830
790
|
|
|
791
|
+
/// Build "Card 4242xxxx4242" (or "Card xxxx4242") from 3DS API status data using card_number or last_4.
|
|
792
|
+
private func formatMaskedCard(from data: [String: Any]?) -> String {
|
|
793
|
+
guard let data = data else { return "Card" }
|
|
794
|
+
let cardNum = (data["card_number"] as? String ?? "").replacingOccurrences(of: " ", with: "")
|
|
795
|
+
if cardNum.count >= 8 {
|
|
796
|
+
let prefix = String(cardNum.prefix(4))
|
|
797
|
+
let suffix = String(cardNum.suffix(4))
|
|
798
|
+
return "Card \(prefix)xxxx\(suffix)"
|
|
799
|
+
}
|
|
800
|
+
if cardNum.count == 4 {
|
|
801
|
+
return "Card xxxx\(cardNum)"
|
|
802
|
+
}
|
|
803
|
+
if let last4 = (data["last_4"] as? String)?.replacingOccurrences(of: " ", with: ""), !last4.isEmpty {
|
|
804
|
+
return "Card xxxx\(last4)"
|
|
805
|
+
}
|
|
806
|
+
return "Card"
|
|
807
|
+
}
|
|
808
|
+
|
|
831
809
|
private func populateInitialLabels() {
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
let
|
|
838
|
-
let
|
|
810
|
+
lblTransactionHeading.text = "3DS reference:"
|
|
811
|
+
lblSubscriptionHeading.text = "Subscription ID:"
|
|
812
|
+
lblReferenceIdHeading.isHidden = true
|
|
813
|
+
lblRefferenceId.isHidden = true
|
|
814
|
+
|
|
815
|
+
let dataSource = (threeDSecureStatusResponse?["data"] as? [String: Any]) ?? chargeData
|
|
816
|
+
let chargeId = dataSource["charge_id"] as? String ?? "N/A"
|
|
817
|
+
let transactionId = dataSource["transaction_id"] as? String ?? "N/A"
|
|
818
|
+
let subscriptionId = dataSource["subscription_id"] as? String
|
|
819
|
+
let status = dataSource["status"] as? String ?? "N/A"
|
|
820
|
+
let dateStringToUse = dateStringForDisplay(dataSource: dataSource, status: status)
|
|
839
821
|
|
|
840
822
|
let formattedDate: String
|
|
841
|
-
if let date = convertToDate(dateString:
|
|
823
|
+
if !dateStringToUse.isEmpty, let date = convertToDate(dateString: dateStringToUse) {
|
|
842
824
|
formattedDate = formatDate(date: date)
|
|
843
825
|
} else {
|
|
844
826
|
formattedDate = "N/A"
|
|
845
827
|
}
|
|
846
828
|
|
|
829
|
+
if !dateStringToUse.isEmpty { displayed3dsDate = formattedDate }
|
|
830
|
+
|
|
847
831
|
lblChargeId.text = chargeId
|
|
848
832
|
lblTransactionId.text = transactionId
|
|
849
|
-
|
|
850
|
-
lblDate.text = formattedDate
|
|
833
|
+
lblDate.text = displayed3dsDate ?? formattedDate
|
|
851
834
|
let rawAmount = Double(request?.amount ?? 0)
|
|
852
835
|
let amountText = String(format: "$%.2f", rawAmount)
|
|
853
836
|
lblTotal.text = amountText
|
|
854
|
-
lblRefferenceId.text = refToken
|
|
855
837
|
lblStatus.text = status
|
|
856
|
-
|
|
838
|
+
|
|
839
|
+
if let subId = subscriptionId, !subId.isEmpty, subId != "null" {
|
|
840
|
+
lblSubscriptionHeading.isHidden = false
|
|
841
|
+
lblSubscriptionId.isHidden = false
|
|
842
|
+
lblSubscriptionId.text = subId
|
|
843
|
+
} else {
|
|
844
|
+
lblSubscriptionHeading.isHidden = true
|
|
845
|
+
lblSubscriptionId.isHidden = true
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
lblPaymentMethodValue.text = formatMaskedCard(from: dataSource)
|
|
857
849
|
}
|
|
858
850
|
|
|
859
851
|
private func startOneMinuteCountdownAndAPICheck() {
|
|
@@ -899,28 +891,37 @@ class ThreeDSecurePaymentDoneVC: BaseVC {
|
|
|
899
891
|
|
|
900
892
|
if let json = self.threeDSecureStatusResponse,
|
|
901
893
|
let data = json["data"] as? [String: Any] {
|
|
902
|
-
|
|
894
|
+
self.lblTransactionHeading.text = "3DS reference:"
|
|
895
|
+
self.lblSubscriptionHeading.text = "Subscription ID:"
|
|
896
|
+
self.lblReferenceIdHeading.isHidden = true
|
|
897
|
+
self.lblRefferenceId.isHidden = true
|
|
903
898
|
let chargeId = data["charge_id"] as? String ?? "N/A"
|
|
904
899
|
let transactionId = data["transaction_id"] as? String ?? "N/A"
|
|
905
|
-
let subscriptionId = data["subscription_id"] as? String
|
|
906
|
-
let
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
900
|
+
let subscriptionId = data["subscription_id"] as? String
|
|
901
|
+
let status = data["status"] as? String ?? ""
|
|
902
|
+
let dateStringToUse = self.dateStringForDisplay(dataSource: data, status: status)
|
|
903
|
+
if !dateStringToUse.isEmpty {
|
|
904
|
+
let formatted: String
|
|
905
|
+
if let date = self.convertToDate(dateString: dateStringToUse) {
|
|
906
|
+
formatted = self.formatDate(date: date)
|
|
907
|
+
} else { formatted = "N/A" }
|
|
908
|
+
self.displayed3dsDate = formatted
|
|
913
909
|
}
|
|
914
|
-
|
|
915
910
|
self.lblChargeId.text = chargeId
|
|
916
911
|
self.lblTransactionId.text = transactionId
|
|
917
|
-
self.
|
|
918
|
-
self.lblDate.text = formattedDate
|
|
912
|
+
self.lblDate.text = self.displayed3dsDate ?? "N/A"
|
|
919
913
|
let rawAmount = Double(self.request?.amount ?? 0)
|
|
920
914
|
let amountText = String(format: "$%.2f", rawAmount)
|
|
921
915
|
self.lblTotal.text = amountText
|
|
922
|
-
|
|
923
|
-
|
|
916
|
+
if let subId = subscriptionId, !subId.isEmpty, subId != "null" {
|
|
917
|
+
self.lblSubscriptionHeading.isHidden = false
|
|
918
|
+
self.lblSubscriptionId.isHidden = false
|
|
919
|
+
self.lblSubscriptionId.text = subId
|
|
920
|
+
} else {
|
|
921
|
+
self.lblSubscriptionHeading.isHidden = true
|
|
922
|
+
self.lblSubscriptionId.isHidden = true
|
|
923
|
+
}
|
|
924
|
+
self.lblPaymentMethodValue.text = self.formatMaskedCard(from: data)
|
|
924
925
|
}
|
|
925
926
|
// Match Android behavior: timeout shows warning UI only.
|
|
926
927
|
}
|
|
@@ -1055,10 +1056,21 @@ class ThreeDSecurePaymentDoneVC: BaseVC {
|
|
|
1055
1056
|
return formatter.date(from: dateString)
|
|
1056
1057
|
}
|
|
1057
1058
|
|
|
1059
|
+
/// When status is "completed", prefer charge_data.date_created; otherwise use created_at.
|
|
1060
|
+
private func dateStringForDisplay(dataSource: [String: Any], status: String) -> String {
|
|
1061
|
+
if status == "completed",
|
|
1062
|
+
let chargeData = dataSource["charge_data"] as? [String: Any],
|
|
1063
|
+
let dateCreated = chargeData["date_created"] as? String, !dateCreated.isEmpty {
|
|
1064
|
+
return dateCreated
|
|
1065
|
+
}
|
|
1066
|
+
return dataSource["created_at"] as? String ?? ""
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
/// Format date for display using UTC so we show server time (created_at) as-is, not local time.
|
|
1058
1070
|
private func formatDate(date: Date) -> String {
|
|
1059
1071
|
let formatter = DateFormatter()
|
|
1060
1072
|
formatter.dateFormat = "dd/MM/yyyy, HH:mm:ss"
|
|
1061
|
-
formatter.timeZone = TimeZone
|
|
1073
|
+
formatter.timeZone = TimeZone(abbreviation: "UTC")
|
|
1062
1074
|
return formatter.string(from: date)
|
|
1063
1075
|
}
|
|
1064
1076
|
|
|
@@ -1085,6 +1097,7 @@ class ThreeDSecurePaymentDoneVC: BaseVC {
|
|
|
1085
1097
|
var uRLRequest = URLRequest(url: url)
|
|
1086
1098
|
uRLRequest.httpMethod = "GET"
|
|
1087
1099
|
uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
1100
|
+
uRLRequest.addValue(APIHeaders.userAgent, forHTTPHeaderField: "User-Agent")
|
|
1088
1101
|
|
|
1089
1102
|
let clientToken = UserStoreSingleton.shared.clientToken ?? ""
|
|
1090
1103
|
uRLRequest.addValue(clientToken, forHTTPHeaderField: "Client-Token")
|
|
@@ -1108,30 +1121,45 @@ class ThreeDSecurePaymentDoneVC: BaseVC {
|
|
|
1108
1121
|
|
|
1109
1122
|
if let dataDict = json["data"] as? [String: Any] {
|
|
1110
1123
|
DispatchQueue.main.async {
|
|
1124
|
+
self.lblTransactionHeading.text = "3DS reference:"
|
|
1125
|
+
self.lblSubscriptionHeading.text = "Subscription ID:"
|
|
1126
|
+
self.lblReferenceIdHeading.isHidden = true
|
|
1127
|
+
self.lblRefferenceId.isHidden = true
|
|
1128
|
+
|
|
1111
1129
|
let chargeId = dataDict["charge_id"] as? String ?? "N/A"
|
|
1112
1130
|
let transactionId = dataDict["transaction_id"] as? String ?? "N/A"
|
|
1113
|
-
let subscriptionId = dataDict["subscription_id"] as? String
|
|
1114
|
-
let createdAt = dataDict["created_at"] as? String ?? ""
|
|
1115
|
-
let referenceId = dataDict["ref_token"] as? String ?? "N/A"
|
|
1131
|
+
let subscriptionId = dataDict["subscription_id"] as? String
|
|
1116
1132
|
let status = dataDict["status"] as? String ?? "N/A"
|
|
1133
|
+
let dateStringToUse = self.dateStringForDisplay(dataSource: dataDict, status: status)
|
|
1117
1134
|
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1135
|
+
if !dateStringToUse.isEmpty {
|
|
1136
|
+
let formatted: String
|
|
1137
|
+
if let date = self.convertToDate(dateString: dateStringToUse) {
|
|
1138
|
+
formatted = self.formatDate(date: date)
|
|
1139
|
+
} else {
|
|
1140
|
+
formatted = "N/A"
|
|
1141
|
+
}
|
|
1142
|
+
self.displayed3dsDate = formatted
|
|
1123
1143
|
}
|
|
1124
1144
|
|
|
1125
1145
|
self.lblChargeId.text = chargeId
|
|
1126
1146
|
self.lblTransactionId.text = transactionId
|
|
1127
|
-
self.
|
|
1128
|
-
self.lblDate.text = formattedDate
|
|
1147
|
+
self.lblDate.text = self.displayed3dsDate ?? "N/A"
|
|
1129
1148
|
let rawAmount = Double(self.request?.amount ?? 0)
|
|
1130
1149
|
let amountText = String(format: "$%.2f", rawAmount)
|
|
1131
1150
|
self.lblTotal.text = amountText
|
|
1132
|
-
self.lblRefferenceId.text = referenceId
|
|
1133
1151
|
self.lblStatus.text = status
|
|
1134
|
-
|
|
1152
|
+
|
|
1153
|
+
if let subId = subscriptionId, !subId.isEmpty, subId != "null" {
|
|
1154
|
+
self.lblSubscriptionHeading.isHidden = false
|
|
1155
|
+
self.lblSubscriptionId.isHidden = false
|
|
1156
|
+
self.lblSubscriptionId.text = subId
|
|
1157
|
+
} else {
|
|
1158
|
+
self.lblSubscriptionHeading.isHidden = true
|
|
1159
|
+
self.lblSubscriptionId.isHidden = true
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
self.lblPaymentMethodValue.text = self.formatMaskedCard(from: dataDict)
|
|
1135
1163
|
|
|
1136
1164
|
// MATCH ANDROID EXACTLY:
|
|
1137
1165
|
// - keep polling ONLY while status == "initiated"
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<svg width="56" height="56" viewBox="0 0 56 56" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M28.3633 17.0916C38.0031 17.0916 45.8183 24.9058 45.8184 34.5457C45.8184 44.1857 38.0031 52.0008 28.3633 52.0008C18.7236 52.0006 10.9092 44.1856 10.9092 34.5457C10.9093 24.906 18.7237 17.0918 28.3633 17.0916ZM28.3643 29.8856C26.6483 29.8856 25.2568 31.277 25.2568 32.993C25.2569 34.2202 25.9697 35.2779 27.0029 35.782C27.1934 35.8749 27.3291 36.0604 27.3291 36.2723V38.7068C27.3291 38.983 27.553 39.2068 27.8291 39.2068H28.9004C29.1765 39.2068 29.4004 38.983 29.4004 38.7068V36.2713C29.4004 36.0596 29.5355 35.8741 29.7256 35.7811C30.7581 35.2767 31.4706 34.2197 31.4707 32.993C31.4707 31.2771 30.08 29.8858 28.3643 29.8856Z" fill="#E9A400"/>
|
|
3
|
+
<path d="M17.8902 21.455V13.6973C17.8902 11.1255 18.9936 8.65901 20.9576 6.84045C22.9216 5.0219 25.5854 4.00024 28.3629 4.00024C31.1405 4.00024 33.8042 5.0219 35.7683 6.84045C37.7323 8.65901 38.8356 11.1255 38.8356 13.6973V21.455" stroke="#E9A400" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
|
|
4
|
+
</svg>
|