@jimrising/easymerchantsdk-react-native 2.5.9 → 2.6.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.
Binary file
@@ -7,7 +7,8 @@ if (configFile.exists()) {
7
7
  }
8
8
 
9
9
  buildscript {
10
- ext.kotlinVersion = "1.9.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.5'
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 ["Content-Type": contentType]
14
+ return [
15
+ "Content-Type": contentType,
16
+ "User-Agent": userAgent
17
+ ]
13
18
  }
14
19
 
15
20
  static func clientTokenHeader() -> [String: String] {
@@ -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()
@@ -8715,10 +8715,10 @@
8715
8715
  <rect key="frame" x="0.0" y="0.0" width="393" height="847.66666666666663"/>
8716
8716
  <subviews>
8717
8717
  <imageView hidden="YES" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="payment_done_icon" translatesAutoresizingMaskIntoConstraints="NO" id="5Tx-1L-CH6">
8718
- <rect key="frame" x="96.666666666666686" y="100" width="200" height="200"/>
8718
+ <rect key="frame" x="96.666666666666686" y="100" width="150" height="150"/>
8719
8719
  <constraints>
8720
- <constraint firstAttribute="width" constant="200" id="hqG-2m-iXd"/>
8721
- <constraint firstAttribute="height" constant="200" id="nf6-x2-tPc"/>
8720
+ <constraint firstAttribute="width" constant="150" id="hqG-2m-iXd"/>
8721
+ <constraint firstAttribute="height" constant="150" id="nf6-x2-tPc"/>
8722
8722
  </constraints>
8723
8723
  <userDefinedRuntimeAttributes>
8724
8724
  <userDefinedRuntimeAttribute type="number" keyPath="cornerRadius">
@@ -8727,10 +8727,10 @@
8727
8727
  </userDefinedRuntimeAttributes>
8728
8728
  </imageView>
8729
8729
  <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="redraw" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="xHM-Ip-TLk">
8730
- <rect key="frame" x="96.666666666666686" y="100" width="200" height="200"/>
8730
+ <rect key="frame" x="96.666666666666686" y="100" width="150" height="150"/>
8731
8731
  <constraints>
8732
- <constraint firstAttribute="height" constant="200" id="JJA-mJ-vdg"/>
8733
- <constraint firstAttribute="width" constant="200" id="ODJ-fO-vJQ"/>
8732
+ <constraint firstAttribute="height" constant="150" id="JJA-mJ-vdg"/>
8733
+ <constraint firstAttribute="width" constant="150" id="ODJ-fO-vJQ"/>
8734
8734
  </constraints>
8735
8735
  <userDefinedRuntimeAttributes>
8736
8736
  <userDefinedRuntimeAttribute type="number" keyPath="cornerRadius">
@@ -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.secureAuthentication == true {
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.secureAuthentication == true {
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.secureAuthentication == true {
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.secureAuthentication == true {
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.secureAuthentication == true {
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.secureAuthentication == true {
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.secureAuthentication == true {
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.secureAuthentication == true {
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.secureAuthentication == true {
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
- // Get the current date and time
207
- let currentDate = Date()
208
- // Format the date and time as "dd/MM/yyyy, HH:mm:ss"
209
- let dateFormatter = DateFormatter()
210
- dateFormatter.dateFormat = "dd/MM/yyyy, HH:mm:ss"
211
- let formattedDate = dateFormatter.string(from: currentDate)
212
- // Set the formatted date and time to the lblDate
213
- lblDate.text = formattedDate
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.secureAuthentication == true {
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.secureAuthentication == true {
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.secureAuthentication == true {
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.secureAuthentication == true {
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.secureAuthentication == true {
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.secureAuthentication == true {
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.secureAuthentication == true {
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.secureAuthentication == true {
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.secureAuthentication == true {
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 showHourglassGIF() {
762
- guard let bundlePath = Bundle.easyPayBundle.path(forResource: "hourglass", ofType: "gif"),
763
- let gifData = try? Data(contentsOf: URL(fileURLWithPath: bundlePath)),
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: "hourglass")?.withRenderingMode(.alwaysTemplate)
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
- showHourglassGIF()
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
- // Populate labels with initial data from chargeData or defaults
833
- let refToken = chargeData["ref_token"] as? String ?? "N/A"
834
- let chargeId = chargeData["charge_id"] as? String ?? "N/A"
835
- let transactionId = chargeData["transaction_id"] as? String ?? "N/A"
836
- let subscriptionId = chargeData["subscription_id"] as? String ?? "N/A"
837
- let createdAt = chargeData["created_at"] as? String ?? ""
838
- let status = chargeData["status"] as? String ?? "N/A"
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: createdAt) {
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
- lblSubscriptionId.text = subscriptionId
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
- lblPaymentMethodValue.text = "Card" // Set to "Card" by default
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
- let refToken = data["ref_token"] as? String ?? "N/A"
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 ?? "N/A"
906
- let createdAt = data["created_at"] as? String ?? ""
907
-
908
- let formattedDate: String
909
- if let date = self.convertToDate(dateString: createdAt) {
910
- formattedDate = self.formatDate(date: date)
911
- } else {
912
- formattedDate = "N/A"
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.lblSubscriptionId.text = subscriptionId
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
- self.lblRefferenceId.text = refToken
923
- self.lblPaymentMethodValue.text = "Card"
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.current
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 ?? "N/A"
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
- let formattedDate: String
1119
- if let date = self.convertToDate(dateString: createdAt) {
1120
- formattedDate = self.formatDate(date: date)
1121
- } else {
1122
- formattedDate = "N/A"
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.lblSubscriptionId.text = subscriptionId
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
- self.lblPaymentMethodValue.text = "Card" // Ensure "Card" in all cases
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,16 @@
1
+ {
2
+ "images" : [
3
+ {
4
+ "filename" : "lock.svg",
5
+ "idiom" : "universal",
6
+ "scale" : "1x"
7
+ }
8
+ ],
9
+ "info" : {
10
+ "author" : "xcode",
11
+ "version" : 1
12
+ },
13
+ "properties" : {
14
+ "template-rendering-intent" : "template"
15
+ }
16
+ }
@@ -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>
@@ -1,6 +1,6 @@
1
1
  Pod::Spec.new do |s|
2
2
  s.name = 'easymerchantsdk'
3
- s.version = '2.5.9'
3
+ s.version = '2.6.1'
4
4
  s.summary = 'A React Native SDK for Easy Merchant.'
5
5
  s.description = <<-DESC
6
6
  A React Native SDK to enable Easy Merchant functionality in mobile applications.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jimrising/easymerchantsdk-react-native",
3
- "version": "2.5.9",
3
+ "version": "2.6.1",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {