@jimrising/easymerchantsdk-react-native 2.2.9 → 2.3.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.
Files changed (102) hide show
  1. package/.idea/caches/deviceStreaming.xml +11 -0
  2. package/README.md +310 -171
  3. package/android/build/.transforms/20e1216b87bd06eaab3c9e5c68d4267a/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/reactlibrary/RNEasymerchantsdkModule$2.dex +0 -0
  4. package/android/build/.transforms/20e1216b87bd06eaab3c9e5c68d4267a/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/reactlibrary/RNEasymerchantsdkModule$3.dex +0 -0
  5. package/android/build/.transforms/20e1216b87bd06eaab3c9e5c68d4267a/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/reactlibrary/RNEasymerchantsdkModule.dex +0 -0
  6. package/android/build/.transforms/20e1216b87bd06eaab3c9e5c68d4267a/transformed/bundleLibRuntimeToDirDebug/desugar_graph.bin +0 -0
  7. package/android/build/.transforms/3f3a228346492fb34e3f574e941b632f/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/com/reactlibrary/RNEasymerchantsdkModule$2.dex +0 -0
  8. package/android/build/.transforms/3f3a228346492fb34e3f574e941b632f/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/com/reactlibrary/RNEasymerchantsdkModule$3.dex +0 -0
  9. package/android/build/.transforms/3f3a228346492fb34e3f574e941b632f/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/com/reactlibrary/RNEasymerchantsdkModule.dex +0 -0
  10. package/android/build/.transforms/3f3a228346492fb34e3f574e941b632f/transformed/bundleLibRuntimeToDirRelease/desugar_graph.bin +0 -0
  11. package/android/build/.transforms/58b147bdc43284da2468dab19bc4a64d/transformed/classes/classes_dex/classes.dex +0 -0
  12. package/android/build/.transforms/e9a664a11ce12edf79cd87b1e07aa243/transformed/classes/classes_dex/classes.dex +0 -0
  13. package/android/build/intermediates/aar_main_jar/release/syncReleaseLibJars/classes.jar +0 -0
  14. package/android/build/intermediates/compile_library_classes_jar/debug/bundleLibCompileToJarDebug/classes.jar +0 -0
  15. package/android/build/intermediates/compile_library_classes_jar/release/bundleLibCompileToJarRelease/classes.jar +0 -0
  16. package/android/build/intermediates/full_jar/release/createFullJarRelease/full.jar +0 -0
  17. package/android/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties +1 -1
  18. package/android/build/intermediates/incremental/lintVitalAnalyzeRelease/release-artifact-dependencies.xml +4 -4
  19. package/android/build/intermediates/incremental/lintVitalAnalyzeRelease/release-artifact-libraries.xml +4 -4
  20. package/android/build/intermediates/incremental/release/mergeReleaseResources/compile-file-map.properties +916 -916
  21. package/android/build/intermediates/incremental/release/mergeReleaseResources/merger.xml +3 -3
  22. package/android/build/intermediates/incremental/release/packageReleaseResources/compile-file-map.properties +1 -1
  23. package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkModule$2.class +0 -0
  24. package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkModule$3.class +0 -0
  25. package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkModule.class +0 -0
  26. package/android/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkModule$2.class +0 -0
  27. package/android/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkModule$3.class +0 -0
  28. package/android/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkModule.class +0 -0
  29. package/android/build/intermediates/lint-cache/lintVitalAnalyzeRelease/maven.google/com/android/tools/build/group-index.xml +22 -0
  30. package/android/build/intermediates/lint_model/release/generateReleaseLintModel/release-artifact-dependencies.xml +4 -4
  31. package/android/build/intermediates/lint_model/release/generateReleaseLintModel/release-artifact-libraries.xml +4 -4
  32. package/android/build/intermediates/lint_vital_lint_model/release/generateReleaseLintVitalModel/release-artifact-dependencies.xml +4 -4
  33. package/android/build/intermediates/lint_vital_lint_model/release/generateReleaseLintVitalModel/release-artifact-libraries.xml +4 -4
  34. package/android/build/intermediates/local_aar_for_lint/release/out.aar +0 -0
  35. package/android/build/intermediates/merged_res/release/mergeReleaseResources/layout/activity_payment_done_url.xml +2 -1
  36. package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/multi-v2/values-night-v8.json +2 -2
  37. package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/multi-v2/values.json +38 -38
  38. package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/anim-v21.json +9 -9
  39. package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/anim.json +24 -24
  40. package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/animator-v21.json +1 -1
  41. package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/animator.json +34 -34
  42. package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/color-night-v8.json +3 -3
  43. package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/color-v31.json +10 -10
  44. package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/color.json +153 -153
  45. package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/drawable-anydpi-v21.json +6 -6
  46. package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/drawable-hdpi-v4.json +13 -13
  47. package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/drawable-ldpi-v4.json +6 -6
  48. package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/drawable-mdpi-v4.json +12 -12
  49. package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/drawable-v21.json +4 -4
  50. package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/drawable-v23.json +7 -7
  51. package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/drawable-v29.json +1 -1
  52. package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/drawable-xhdpi-v4.json +12 -12
  53. package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/drawable-xxhdpi-v4.json +7 -7
  54. package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/drawable-xxxhdpi-v4.json +7 -7
  55. package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/drawable.json +395 -395
  56. package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/font.json +9 -9
  57. package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/interpolator-v21.json +10 -10
  58. package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/interpolator.json +11 -11
  59. package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/layout-land.json +3 -3
  60. package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/layout-sw600dp-v13.json +2 -2
  61. package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/layout-v21.json +4 -4
  62. package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/layout-v26.json +1 -1
  63. package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/layout.json +108 -108
  64. package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/mipmap-anydpi-v26.json +2 -2
  65. package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/mipmap-hdpi-v4.json +2 -2
  66. package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/mipmap-mdpi-v4.json +2 -2
  67. package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/mipmap-xhdpi-v4.json +2 -2
  68. package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/mipmap-xxhdpi-v4.json +2 -2
  69. package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/mipmap-xxxhdpi-v4.json +2 -2
  70. package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/raw.json +46 -46
  71. package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/xml.json +3 -3
  72. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/RNEasymerchantsdkModule$2.class +0 -0
  73. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/RNEasymerchantsdkModule$3.class +0 -0
  74. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/RNEasymerchantsdkModule.class +0 -0
  75. package/android/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/com/reactlibrary/RNEasymerchantsdkModule$2.class +0 -0
  76. package/android/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/com/reactlibrary/RNEasymerchantsdkModule$3.class +0 -0
  77. package/android/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/com/reactlibrary/RNEasymerchantsdkModule.class +0 -0
  78. package/android/build/intermediates/runtime_library_classes_jar/debug/bundleLibRuntimeToJarDebug/classes.jar +0 -0
  79. package/android/build/intermediates/runtime_library_classes_jar/release/bundleLibRuntimeToJarRelease/classes.jar +0 -0
  80. package/android/build/intermediates/source_set_path_map/release/mapReleaseSourceSetPaths/file-map.txt +38 -38
  81. package/android/build/intermediates/verified_library_resources/release/verifyReleaseResources/compiled/layout_activity_payment_done_url.xml.flat +0 -0
  82. package/android/build/outputs/aar/jimrising_easymerchantsdk-react-native-release.aar +0 -0
  83. package/android/build/tmp/compileDebugJavaWithJavac/previous-compilation-data.bin +0 -0
  84. package/android/build/tmp/compileReleaseJavaWithJavac/previous-compilation-data.bin +0 -0
  85. package/android/build.gradle +1 -1
  86. package/android/src/main/java/com/reactlibrary/RNEasymerchantsdkModule.java +56 -1
  87. package/ios/Classes/EasyMerchantSdk.m +124 -141
  88. package/ios/Classes/EasyMerchantSdk.swift +270 -502
  89. package/ios/Classes/EasyPayViewController.swift +18 -15
  90. package/ios/CustomComponents/TextFieldStackView.swift +16 -46
  91. package/ios/EnvironmentConfig.swift +16 -0
  92. package/ios/Example/ViewController.swift +513 -76
  93. package/ios/Models/Request.swift +365 -171
  94. package/ios/Pods/UserDefaults/UserStoreSingleton.swift +116 -12
  95. package/ios/Pods/ViewControllers/AdditionalInfoVC.swift +2 -32
  96. package/ios/Pods/ViewControllers/GrailPayVC.swift +258 -18
  97. package/ios/Pods/ViewControllers/OTPVerificationVC.swift +1 -66
  98. package/ios/Pods/ViewControllers/PaymentInformation/PaymentInfoVC.swift +571 -369
  99. package/ios/Pods/ViewControllers/ThreeDSecurePaymentDoneVC.swift +18 -9
  100. package/ios/easymerchantsdk.podspec +1 -1
  101. package/ios/easymerchantsdk.storyboard +108 -85
  102. package/package.json +1 -1
@@ -59,9 +59,9 @@ public struct ThemeConfiguration: Codable {
59
59
  public struct GrailPayRequest: Codable {
60
60
  // public let accessToken: String
61
61
  // public let vendorId: String
62
+ // public let isSandbox: Bool
62
63
  public let role: String
63
64
  public let timeout: Int
64
- public let isSandbox: Bool
65
65
  public let brandingName: String
66
66
  public let finderSubtitle: String
67
67
  public let searchPlaceholder: String
@@ -69,18 +69,18 @@ public struct GrailPayRequest: Codable {
69
69
  public init(
70
70
  // accessToken: String,
71
71
  // vendorId: String,
72
+ // isSandbox: Bool,
72
73
  role: String,
73
74
  timeout: Int,
74
- isSandbox: Bool,
75
75
  brandingName: String,
76
76
  finderSubtitle: String,
77
77
  searchPlaceholder: String
78
78
  ) {
79
79
  // self.accessToken = accessToken
80
80
  // self.vendorId = vendorId
81
+ // self.isSandbox = isSandbox
81
82
  self.role = role
82
83
  self.timeout = timeout
83
- self.isSandbox = isSandbox
84
84
  self.brandingName = brandingName
85
85
  self.finderSubtitle = finderSubtitle
86
86
  self.searchPlaceholder = searchPlaceholder
@@ -146,11 +146,11 @@ public struct FieldSection: Codable {
146
146
 
147
147
  @objc
148
148
  public final class Request: NSObject {
149
- public let amount: Double?
149
+ public var amount: Double?
150
150
  public let currency: String?
151
151
  public let fields: Data?
152
152
  public let appearanceSettings: ThemeConfiguration?
153
- public let selectedPaymentMethods: [PaymentMethod]
153
+ public var selectedPaymentMethods: [PaymentMethod]
154
154
  public let tokenOnly: Bool?
155
155
  public let saveCard: Bool?
156
156
  public let saveAccount: Bool?
@@ -172,8 +172,11 @@ public final class Request: NSObject {
172
172
  public let email: String?
173
173
  public let name: String?
174
174
 
175
+ public let clientToken: String?
176
+
175
177
  public var metadata: [String: Any]?
176
-
178
+ public private(set) var initializationErrorMessage: String?
179
+
177
180
  public init(
178
181
  amount: Double? = nil,
179
182
  currency: String? = nil,
@@ -200,28 +203,13 @@ public final class Request: NSObject {
200
203
  isEmail: Bool = false,
201
204
  email: String? = nil,
202
205
  name: String? = nil,
206
+ clientToken: String? = nil,
207
+ metadata: [String: Any]? = nil,
203
208
 
204
- metadata: [String: Any]? = nil
209
+ environment: EnvironmentConfig.Environment? = nil,
210
+ apiKey: String? = nil,
211
+ apiSecret: String? = nil
205
212
  ) {
206
- // Validate if amount is provided, must be ≥ 0.50
207
- if let amt = amount, amt < 0.50 {
208
- DispatchQueue.main.async {
209
- if let topVC = UIApplication.topViewController() {
210
- let alert = UIAlertController(title: "Invalid Amount",
211
- message: "Amount must be at least $0.50.",
212
- preferredStyle: .alert)
213
- alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { _ in
214
- if let easyPayVC = UIApplication.findEasyPayViewController(from: topVC) {
215
- easyPayVC.dismiss(animated: true)
216
- } else {
217
- // fallback: dismiss topVC itself if it's presented modally
218
- topVC.dismiss(animated: true)
219
- }
220
- }))
221
- topVC.present(alert, animated: true)
222
- }
223
- }
224
- }
225
213
 
226
214
  self.amount = amount
227
215
  self.currency = currency
@@ -248,8 +236,20 @@ public final class Request: NSObject {
248
236
  self.email = email
249
237
  self.name = name
250
238
 
239
+ self.clientToken = clientToken
240
+
251
241
  self.metadata = metadata
252
242
 
243
+ // Configure environment if provided
244
+ if let environment = environment {
245
+ EnvironmentConfig.setEnvironment(environment)
246
+ }
247
+
248
+ // Configure API credentials if provided
249
+ if let key = apiKey, let secret = apiSecret {
250
+ EnvironmentConfig.configure(apiKey: key, apiSecret: secret)
251
+ }
252
+
253
253
  // Conditionally assign recurring fields only if is_recurring == true
254
254
  if is_recurring {
255
255
  self.is_recurring = true
@@ -289,21 +289,127 @@ public final class Request: NSObject {
289
289
  }
290
290
 
291
291
  super.init()
292
+
293
+ // Environment must be set by host app
294
+ // if EnvironmentConfig.isEnvironmentSet == false {
295
+ // self.initializationErrorMessage = "environment not set"
296
+ // Request.notifyValidationError(self.initializationErrorMessage!)
297
+ // return
298
+ // }
299
+
300
+ // ✅ Override amount if clientToken flow is used
301
+ if let token = clientToken, !token.isEmpty {
302
+ if let savedAmount = UserStoreSingleton.shared.amount,
303
+ let doubleAmount = Double(savedAmount) {
304
+ self.amount = doubleAmount
305
+ }
306
+ }
307
+
308
+ // API keys presence validation for non-clientToken case
309
+ if clientToken == nil {
310
+ let hasKey = (EnvironmentConfig.apiKey?.isEmpty == false)
311
+ let hasSecret = (EnvironmentConfig.apiSecret?.isEmpty == false)
312
+ if !hasKey && !hasSecret {
313
+ self.initializationErrorMessage = "Api Key and Secret not found"
314
+ Request.notifyValidationError(self.initializationErrorMessage!)
315
+ return
316
+ }
317
+ if hasKey && !hasSecret {
318
+ self.initializationErrorMessage = "Secret Key not found"
319
+ Request.notifyValidationError(self.initializationErrorMessage!)
320
+ return
321
+ }
322
+ if !hasKey && hasSecret {
323
+ self.initializationErrorMessage = "Api Key not found"
324
+ Request.notifyValidationError(self.initializationErrorMessage!)
325
+ return
326
+ }
327
+ } else {
328
+ // Validate clientToken is not empty
329
+ if clientToken?.isEmpty ?? true {
330
+ self.initializationErrorMessage = "Client token cannot be empty"
331
+ Request.notifyValidationError(self.initializationErrorMessage!)
332
+ return
333
+ }
334
+ }
335
+
336
+ // Payment methods: at least one required if provided
337
+ if let pm = paymentMethods, pm.isEmpty {
338
+ self.initializationErrorMessage = "paymentMethods param minimum one payment method required"
339
+ Request.notifyValidationError(self.initializationErrorMessage!)
340
+ return
341
+ }
342
+
343
+ // Validate amount (must be ≥ 0.50)
344
+ if let amt = amount, amt < 0.50 {
345
+ self.initializationErrorMessage = "Amount must be at least $0.50."
346
+ Request.notifyValidationError(self.initializationErrorMessage!)
347
+ return
348
+ }
349
+
350
+ // ✅ Use recurring details from HostedCheckout API only if clientToken is present
351
+ if let token = clientToken, !token.isEmpty {
352
+ if let savedIsRecurring = UserStoreSingleton.shared.isRecurring,
353
+ savedIsRecurring == "1" { // API returns "1" for true
354
+ self.is_recurring = true
355
+
356
+ if let cycles = UserStoreSingleton.shared.allowCycles,
357
+ let num = Int(cycles) {
358
+ self.numOfCycle = num
359
+ }
360
+
361
+ if let intervals = UserStoreSingleton.shared.interval {
362
+ // API gives "daily,weekly,monthly" → split into array
363
+ let parts = intervals.split(separator: ",").map { String($0).trimmingCharacters(in: .whitespaces) }
364
+ self.recurringIntervals = parts.compactMap { RecurringIntervals(rawValue: $0) }
365
+ }
366
+
367
+ if let startType = UserStoreSingleton.shared.startDateType {
368
+ self.recurringStartDateType = RecurringStartDateType(rawValue: startType)
369
+ }
370
+
371
+ if let startDate = UserStoreSingleton.shared.startDate {
372
+ self.recurringStartDate = startDate
373
+ }
374
+ } else {
375
+ self.is_recurring = false
376
+ }
377
+ }
378
+
379
+ // Validate recurring start date not in past
380
+ if let startDateString = recurringStartDate,
381
+ let startDate = DateFormatter.recurringDateFormatter.date(from: startDateString) {
382
+ let today = Calendar.current.startOfDay(for: Date())
383
+ let startDay = Calendar.current.startOfDay(for: startDate)
384
+ if startDay < today {
385
+ self.initializationErrorMessage = "The recurring start date cannot be in the past. Please select today or a future date."
386
+ Request.notifyValidationError(self.initializationErrorMessage!)
387
+ return
388
+ }
389
+ } else if recurringStartDate != nil {
390
+ // Provided but invalid format
391
+ self.initializationErrorMessage = "Recurring date format should be dd/MM/yyyy"
392
+ Request.notifyValidationError(self.initializationErrorMessage!)
393
+ return
394
+ }
292
395
 
293
396
  // Validate metadata limits
294
397
  if let metadata = metadata {
295
398
  if metadata.count > 50 {
296
- Request.showErrorAndDismissForMetaData(message: "Metadata must contain no more than 50 keys.")
399
+ self.initializationErrorMessage = "Metadata must contain no more than 50 keys."
400
+ Request.notifyValidationError(self.initializationErrorMessage!)
297
401
  return
298
402
  }
299
403
 
300
404
  for (key, value) in metadata {
301
405
  if key.count > 40 {
302
- Request.showErrorAndDismissForMetaData(message: "Metadata key '\(key)' exceeds 40 characters.")
406
+ self.initializationErrorMessage = "Metadata key '\(key)' exceeds 40 characters."
407
+ Request.notifyValidationError(self.initializationErrorMessage!)
303
408
  return
304
409
  }
305
410
  if let stringValue = value as? String, stringValue.count > 500 {
306
- Request.showErrorAndDismissForMetaData(message: "Value for key '\(key)' exceeds 500 characters.")
411
+ self.initializationErrorMessage = "Value for key '\(key)' exceeds 500 characters."
412
+ Request.notifyValidationError(self.initializationErrorMessage!)
307
413
  return
308
414
  }
309
415
  }
@@ -313,50 +419,49 @@ public final class Request: NSObject {
313
419
  self.metadata = nil
314
420
  }
315
421
 
316
- // Validate recurring intervals
422
+ // Validate recurring intervals and required recurring params
317
423
  if is_recurring == true {
318
- let intervals = recurringIntervals ?? []
424
+ // if numOfCycle == nil { self.initializationErrorMessage = "numOfCycle object not defined"; Request.notifyValidationError(self.initializationErrorMessage!); return }
319
425
 
320
- if intervals.count < 2 {
321
- DispatchQueue.main.async {
322
- if let topVC = UIApplication.topViewController() {
323
- let alert = UIAlertController(
324
- title: "Recurring Interval Required",
325
- message: "Please add minimum two intervals for recurring.",
326
- preferredStyle: .alert
327
- )
328
- alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { _ in
329
- if let easyPayVC = UIApplication.findEasyPayViewController(from: topVC) {
330
- easyPayVC.dismiss(animated: true)
331
- } else {
332
- topVC.dismiss(animated: true)
333
- }
334
- }))
335
- topVC.present(alert, animated: true)
336
- }
426
+ // ✅ Skip numOfCycle validation if clientToken is provided and not empty
427
+ if (clientToken == nil || clientToken?.isEmpty == true) {
428
+ if numOfCycle == nil {
429
+ self.initializationErrorMessage = "numOfCycle object not defined"
430
+ Request.notifyValidationError(self.initializationErrorMessage!)
431
+ return
337
432
  }
433
+ }
434
+
435
+ if recurringIntervals == nil { self.initializationErrorMessage = "recurringIntervals object not defined"; Request.notifyValidationError(self.initializationErrorMessage!); return }
436
+ if recurringStartDateType == nil { self.initializationErrorMessage = "recurringStartDateType object not defined"; Request.notifyValidationError(self.initializationErrorMessage!); return }
437
+ if recurringStartDate == nil { self.initializationErrorMessage = "recurringStartDate object not defined"; Request.notifyValidationError(self.initializationErrorMessage!); return }
438
+ // Check cycle count
439
+ if let cycles = numOfCycle, cycles < 2 {
440
+ self.initializationErrorMessage = "Number of cycle count for recurring must be minimum 2."
441
+ Request.notifyValidationError(self.initializationErrorMessage!)
338
442
  return
339
443
  }
340
-
444
+
445
+ let intervals = recurringIntervals ?? []
446
+ // if intervals.count < 2 {
447
+ // self.initializationErrorMessage = "Please add minimum two intervals for recurring."
448
+ // Request.notifyValidationError(self.initializationErrorMessage!)
449
+ // return
450
+ // }
451
+
341
452
  let uniqueIntervals = Set(intervals)
342
453
  if uniqueIntervals.count < intervals.count {
343
- DispatchQueue.main.async {
344
- if let topVC = UIApplication.topViewController() {
345
- let alert = UIAlertController(
346
- title: "Duplicate Intervals",
347
- message: "Duplicate interval values",
348
- preferredStyle: .alert
349
- )
350
- alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { _ in
351
- if let easyPayVC = UIApplication.findEasyPayViewController(from: topVC) {
352
- easyPayVC.dismiss(animated: true)
353
- } else {
354
- topVC.dismiss(animated: true)
355
- }
356
- }))
357
- topVC.present(alert, animated: true)
358
- }
359
- }
454
+ self.initializationErrorMessage = "Duplicate interval values"
455
+ Request.notifyValidationError(self.initializationErrorMessage!)
456
+ return
457
+ }
458
+ }
459
+
460
+ // GrailPay validations when authenticatedACH is true
461
+ if authenticatedACH == true {
462
+ if grailPayParams == nil {
463
+ self.initializationErrorMessage = "grailPayParams should be json object"
464
+ Request.notifyValidationError(self.initializationErrorMessage!)
360
465
  return
361
466
  }
362
467
  }
@@ -372,12 +477,27 @@ public final class Request: NSObject {
372
477
  }
373
478
  }
374
479
  else {
375
- // Proceed with normal payment flow
376
- self.paymentIntentApi { success in
377
- if success {
378
- print("Payment Intent API call succeeded")
379
- UserStoreSingleton.shared.price = String(self.amount ?? 0)
380
- print("Saved amount: \(UserStoreSingleton.shared.price ?? "No amount")")
480
+ if clientToken == nil {
481
+ // Proceed with normal payment flow for API key/secret case
482
+ self.paymentIntentApi { success in
483
+ if success {
484
+ print("Payment Intent API call succeeded")
485
+ UserStoreSingleton.shared.price = String(self.amount ?? 0)
486
+ print("Saved amount: \(UserStoreSingleton.shared.price ?? "No amount")")
487
+ } else {
488
+ print("Payment Intent API call failed")
489
+ }
490
+ }
491
+ } else {
492
+ // For clientToken case, directly call hostedCheckoutsApi
493
+ UserStoreSingleton.shared.clientToken = clientToken
494
+ self.hostedCheckoutsApi { success in
495
+ if success {
496
+ print("Hosted Checkout API call succeeded")
497
+ UserStoreSingleton.shared.price = String(self.amount ?? 0)
498
+ } else {
499
+ print("Hosted Checkout API call failed")
500
+ }
381
501
  }
382
502
  }
383
503
  }
@@ -394,7 +514,8 @@ public final class Request: NSObject {
394
514
  //MARK: - Payment Intent Api
395
515
  func paymentIntentApi(completion: @escaping (Bool) -> Void) {
396
516
  guard let serviceURL = URL(string: EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.paymentIntent.path()) else {
397
- showErrorAndDismiss(message: "Invalid payment URL.")
517
+ Request.notifyValidationError("Invalid payment URL.")
518
+ print("Invalid payment URL for Payment Intent API")
398
519
  completion(false)
399
520
  return
400
521
  }
@@ -412,10 +533,16 @@ public final class Request: NSObject {
412
533
  let startDay = Calendar.current.startOfDay(for: startDate)
413
534
 
414
535
  if startDay < today {
415
- showErrorAndDismiss(message: "The recurring start date cannot be in the past. Please select today or a future date.")
536
+ Request.notifyValidationError("The recurring start date cannot be in the past. Please select today or a future date.")
537
+ print("Recurring start date validation failed")
416
538
  completion(false)
417
539
  return
418
540
  }
541
+ } else if recurringStartDate != nil {
542
+ Request.notifyValidationError("Recurring date format should be dd/MM/yyyy")
543
+ print("Invalid recurring date format")
544
+ completion(false)
545
+ return
419
546
  }
420
547
 
421
548
  let params: [String: Any] = [
@@ -427,94 +554,102 @@ public final class Request: NSObject {
427
554
  "recurring_start_date_type": recurringStartDateType?.rawValue ?? ""
428
555
  ]
429
556
 
430
- print(params)
557
+ print("Payment Intent API Request Params: \(params)")
431
558
 
432
559
  do {
433
560
  request.httpBody = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
434
561
  } catch {
435
- showErrorAndDismiss(message: "Failed to encode payment data.")
562
+ Request.notifyValidationError("Failed to encode Payment Intent API data: \(error.localizedDescription)")
563
+ print("Error encoding Payment Intent API data: \(error)")
436
564
  completion(false)
437
565
  return
438
566
  }
439
567
 
440
568
  let task = URLSession.shared.dataTask(with: request) { data, response, error in
441
- guard let httpResponse = response as? HTTPURLResponse, error == nil else {
442
- self.showErrorAndDismiss(message: error?.localizedDescription ?? "An unknown error occurred.")
569
+ if let error = error {
570
+ Request.notifyValidationError("Payment Intent API error: \(error.localizedDescription)")
571
+ print("Payment Intent API error: \(error)")
443
572
  completion(false)
444
573
  return
445
574
  }
446
575
 
576
+ guard let httpResponse = response as? HTTPURLResponse else {
577
+ Request.notifyValidationError("Invalid response from Payment Intent API")
578
+ print("Invalid response from Payment Intent API")
579
+ completion(false)
580
+ return
581
+ }
582
+
583
+ print("Payment Intent API Response Status Code: \(httpResponse.statusCode)")
584
+
447
585
  if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
448
586
  if let data = data {
449
587
  do {
450
588
  if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
589
+ print("Payment Intent API Response: \(responseObject)")
451
590
  if let clientToken = responseObject["client_token"] as? String {
452
591
  UserStoreSingleton.shared.clientToken = clientToken
453
- // print(clientToken)
592
+ print("Received clientToken: \(clientToken)")
593
+ } else {
594
+ Request.notifyValidationError("No client_token in Payment Intent API response")
595
+ print("No client_token in Payment Intent API response")
596
+ completion(false)
597
+ return
454
598
  }
455
599
  if let paymentIntent = responseObject["payment_intent"] as? String {
456
600
  UserStoreSingleton.shared.paymentIntent = paymentIntent
601
+ print("Received paymentIntent: \(paymentIntent)")
457
602
  }
458
603
  self.hostedCheckoutsApi { success in
459
604
  completion(success)
460
605
  }
461
606
  return
462
607
  } else {
463
- self.showErrorAndDismiss(message: "Invalid response format.")
608
+ Request.notifyValidationError("Invalid response format from Payment Intent API")
609
+ print("Invalid response format from Payment Intent API")
610
+ completion(false)
464
611
  }
465
612
  } catch {
466
- self.showErrorAndDismiss(message: "Failed to parse response.")
613
+ Request.notifyValidationError("Failed to parse Payment Intent API response: \(error.localizedDescription)")
614
+ print("Error parsing Payment Intent API response: \(error)")
615
+ completion(false)
467
616
  }
468
617
  } else {
469
- self.showErrorAndDismiss(message: "No response data received.")
618
+ Request.notifyValidationError("No response data received from Payment Intent API")
619
+ print("No response data received from Payment Intent API")
620
+ completion(false)
470
621
  }
471
622
  } else {
472
- var message = "Payment request failed with status code: \(httpResponse.statusCode)"
623
+ var message = "Payment Intent API failed with status code: \(httpResponse.statusCode)"
473
624
  if let data = data,
474
625
  let responseObj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
475
- let msg = responseObj["message"] as? String {
626
+ let msg = responseObj["message"] as? String, !msg.isEmpty {
476
627
  message = msg
477
628
  }
478
- self.showErrorAndDismiss(message: message)
629
+ Request.notifyValidationError(message)
630
+ print("Payment Intent API error: \(message)")
631
+ completion(false)
479
632
  }
480
- completion(false)
481
633
  }
482
634
  task.resume()
483
635
  }
484
-
636
+
485
637
  private static func showErrorAndDismissForMetaData(message: String) {
486
- DispatchQueue.main.async {
487
- if let topVC = UIApplication.topViewController() {
488
- let alert = UIAlertController(title: "Payment Error",
489
- message: message,
490
- preferredStyle: .alert)
491
- alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { _ in
492
- if let easyPayVC = UIApplication.findEasyPayViewController(from: topVC) {
493
- easyPayVC.dismiss(animated: true)
494
- } else {
495
- topVC.dismiss(animated: true)
496
- }
497
- }))
498
- topVC.present(alert, animated: true)
499
- }
500
- }
638
+ Request.notifyValidationError(message)
639
+ }
640
+
641
+ private func showErrorAndDismiss(message: String) {
642
+ Request.notifyValidationError(message)
501
643
  }
502
644
 
503
- private func showErrorAndDismiss(message: String) {
645
+ private static func notifyValidationError(_ message: String) {
504
646
  DispatchQueue.main.async {
505
- if let topVC = UIApplication.topViewController() {
506
- let alert = UIAlertController(title: "Payment Error",
507
- message: message,
508
- preferredStyle: .alert)
509
- alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { _ in
510
- if let easyPayVC = UIApplication.findEasyPayViewController(from: topVC) {
511
- easyPayVC.dismiss(animated: true)
512
- } else {
513
- topVC.dismiss(animated: true)
514
- }
515
- }))
516
- topVC.present(alert, animated: true)
517
- }
647
+ let chargeData: [String: Any] = [
648
+ "status": false,
649
+ "message": message
650
+ ]
651
+ let result = SDKResult(type: .error, chargeData: chargeData, billingInfo: nil, additionalInfo: nil)
652
+ NotificationCenter.default.post(name: NSNotification.Name("EasyPayResult"), object: result)
518
653
  }
519
654
  }
520
655
 
@@ -522,7 +657,8 @@ public final class Request: NSObject {
522
657
  func hostedCheckoutsApi(completion: @escaping (Bool) -> Void) {
523
658
  // Build the URL using EnvironmentConfig
524
659
  guard let baseURL = URL(string: EnvironmentConfig.baseURL) else {
525
- print("Invalid base URL")
660
+ Request.notifyValidationError("Invalid base URL for Hosted Checkout API")
661
+ print("Invalid base URL for Hosted Checkout API")
526
662
  completion(false)
527
663
  return
528
664
  }
@@ -533,36 +669,47 @@ public final class Request: NSObject {
533
669
  var request = URLRequest(url: serviceURL)
534
670
  request.httpMethod = "POST"
535
671
  request.addValue("application/json", forHTTPHeaderField: "Content-Type")
536
- request.addValue(UserStoreSingleton.shared.clientToken ?? "", forHTTPHeaderField: "clientToken")
537
672
 
538
- let params: [String: Any] = [
539
- "amount": ((amount ?? 0) * 100).rounded() / 100,
540
- ]
541
-
542
- print(params)
543
-
544
- do {
545
- request.httpBody = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
546
- } catch {
547
- print("Error creating JSON data: \(error)")
673
+ let token = clientToken ?? UserStoreSingleton.shared.clientToken ?? ""
674
+ if token.isEmpty {
675
+ Request.notifyValidationError("No valid client token provided for Hosted Checkout API")
676
+ print("No valid client token provided for Hosted Checkout API")
548
677
  completion(false)
549
678
  return
550
679
  }
680
+ request.addValue(token, forHTTPHeaderField: "clientToken")
681
+
682
+ // print("➡️ Hosted Checkout API Request URL: \(serviceURL.absoluteString)")
683
+ // print("➡️ Hosted Checkout API clientToken: \(token)")
684
+ // print("➡️ Headers: \(request.allHTTPHeaderFields ?? [:])")
551
685
 
552
686
  let task = URLSession.shared.dataTask(with: request) { data, response, error in
687
+ if let error = error {
688
+ Request.notifyValidationError("Hosted Checkout API error: \(error.localizedDescription)")
689
+ print("Hosted Checkout API error: \(error)")
690
+ completion(false)
691
+ return
692
+ }
553
693
 
554
- guard let httpResponse = response as? HTTPURLResponse, error == nil else {
555
- print("Error: \(error?.localizedDescription ?? "Unknown error")")
694
+ guard let httpResponse = response as? HTTPURLResponse else {
695
+ Request.notifyValidationError("Invalid response from Hosted Checkout API")
696
+ print("Invalid response from Hosted Checkout API")
556
697
  completion(false)
557
698
  return
558
699
  }
559
700
 
701
+ print("Hosted Checkout API Response Status Code: \(httpResponse.statusCode)")
702
+
560
703
  if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
561
704
  if let data = data {
562
705
  do {
563
706
  if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
564
707
  let dataObject = responseObject["data"] as? [String: Any] {
565
- print(dataObject)
708
+ print("Hosted Checkout API Response: \(dataObject)")
709
+
710
+ // Persist the exact token used for the request (header value),
711
+ // not self.clientToken which may be nil on first-run flows.
712
+ UserStoreSingleton.shared.clientToken = token
566
713
 
567
714
  // Save merchant details
568
715
  if let merchantEmail = dataObject["merchant_email"] as? String {
@@ -624,6 +771,40 @@ public final class Request: NSObject {
624
771
  print("Saved payment methods: \(paymentMethodNames)")
625
772
  }
626
773
 
774
+ // Save payment intent amount
775
+ if let paymentAmount = dataObject["payment_intent_amount"] {
776
+ UserStoreSingleton.shared.amount = "\(paymentAmount)"
777
+ print("Saved payment_intent_amount: \(paymentAmount)")
778
+ }
779
+
780
+ // Save recurring details
781
+ if let recurringDetails = dataObject["recurring_details"] as? [String: Any] {
782
+ if let allowedCycles = recurringDetails["allowed_cycles"] {
783
+ UserStoreSingleton.shared.allowCycles = "\(allowedCycles)"
784
+ print("Saved allowed_cycles: \(allowedCycles)")
785
+ }
786
+ if let interval = recurringDetails["interval"] {
787
+ UserStoreSingleton.shared.interval = "\(interval)"
788
+ print("Saved interval: \(interval)")
789
+ }
790
+ if let isRecurring = recurringDetails["is_recurring"] {
791
+ UserStoreSingleton.shared.isRecurring = "\(isRecurring)"
792
+ print("Saved is_recurring: \(isRecurring)")
793
+ }
794
+ if let paymentIntentId = recurringDetails["payment_intent_id"] {
795
+ UserStoreSingleton.shared.paymentIntentId = "\(paymentIntentId)"
796
+ print("Saved payment_intent_id: \(paymentIntentId)")
797
+ }
798
+ if let startDate = recurringDetails["start_date"] {
799
+ UserStoreSingleton.shared.startDate = "\(startDate)"
800
+ print("Saved start_date: \(startDate)")
801
+ }
802
+ if let startDateType = recurringDetails["start_date_type"] {
803
+ UserStoreSingleton.shared.startDateType = "\(startDateType)"
804
+ print("Saved start_date_type: \(startDateType)")
805
+ }
806
+ }
807
+
627
808
  if self.appearanceSettings == nil, let appearanceSettings = dataObject["apperance_settings"] as? [String: Any] {
628
809
  UserStoreSingleton.shared.body_bg_col = appearanceSettings["body_bg_col"] as? String
629
810
  UserStoreSingleton.shared.border_radious = appearanceSettings["border_radious"] as? String
@@ -637,26 +818,34 @@ public final class Request: NSObject {
637
818
  UserStoreSingleton.shared.secondary_btn_hover_col = appearanceSettings["secondary_btn_hover_col"] as? String
638
819
  UserStoreSingleton.shared.secondary_font_col = appearanceSettings["secondary_font_col"] as? String
639
820
 
640
- // Clearing the font size value
641
821
  UserStoreSingleton.shared.fontSize = nil
642
822
  }
643
823
 
644
824
  completion(true)
645
-
646
825
  } else {
647
- print("Invalid JSON format or 'data' key is missing")
826
+ Request.notifyValidationError("Invalid JSON format or 'data' key missing in Hosted Checkout API response")
827
+ print("Invalid JSON format or 'data' key missing in Hosted Checkout API response")
648
828
  completion(false)
649
829
  }
650
830
  } catch {
651
- print("Error parsing JSON: \(error)")
831
+ Request.notifyValidationError("Failed to parse Hosted Checkout API response: \(error.localizedDescription)")
832
+ print("Error parsing Hosted Checkout API response: \(error)")
652
833
  completion(false)
653
834
  }
654
835
  } else {
655
- print("No data received")
836
+ Request.notifyValidationError("No response data received from Hosted Checkout API")
837
+ print("No response data received from Hosted Checkout API")
656
838
  completion(false)
657
839
  }
658
840
  } else {
659
- print("HTTP Status Code: \(httpResponse.statusCode)")
841
+ var message = "Hosted Checkout API failed with status code: \(httpResponse.statusCode)"
842
+ if let data = data,
843
+ let responseObj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
844
+ let msg = responseObj["message"] as? String, !msg.isEmpty {
845
+ message = msg
846
+ }
847
+ Request.notifyValidationError(message)
848
+ print("Hosted Checkout API error: \(message)")
660
849
  completion(false)
661
850
  }
662
851
  }
@@ -678,9 +867,12 @@ public final class Request: NSObject {
678
867
  uRLRequest.httpMethod = "GET"
679
868
  uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
680
869
 
681
- let token = UserStoreSingleton.shared.customerToken
682
- print("Setting customerToken header: \(token ?? "None")")
683
- uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Customer-Token")
870
+ // let tokenn = UserStoreSingleton.shared.customerToken
871
+ // print("Setting customerToken header: \(tokenn ?? "None")")
872
+ // uRLRequest.addValue(tokenn ?? "", forHTTPHeaderField: "Customer-Token")
873
+
874
+ let token = clientToken ?? UserStoreSingleton.shared.clientToken ?? ""
875
+ uRLRequest.addValue(token, forHTTPHeaderField: "clientToken")
684
876
 
685
877
  // Add API key/secret headers
686
878
  if let apiKey = EnvironmentConfig.apiKey,
@@ -722,41 +914,43 @@ public final class Request: NSObject {
722
914
  extension DateFormatter {
723
915
  static let recurringDateFormatter: DateFormatter = {
724
916
  let formatter = DateFormatter()
725
- formatter.dateFormat = "MM/dd/yyyy"
917
+ formatter.dateFormat = "dd/MM/yyyy"
726
918
  formatter.timeZone = .current
727
919
  return formatter
728
920
  }()
729
921
  }
730
922
 
731
- extension UIApplication {
732
- static func topViewController(base: UIViewController? = UIApplication.shared.connectedScenes
733
- .compactMap { ($0 as? UIWindowScene)?.windows.first(where: \.isKeyWindow) }
734
- .first?.rootViewController) -> UIViewController? {
735
- if let nav = base as? UINavigationController {
736
- return topViewController(base: nav.visibleViewController)
737
- }
738
- if let tab = base as? UITabBarController {
739
- if let selected = tab.selectedViewController {
740
- return topViewController(base: selected)
741
- }
742
- }
743
- if let presented = base?.presentedViewController {
744
- return topViewController(base: presented)
745
- }
746
- return base
747
- }
748
- }
749
923
 
750
- extension UIApplication {
751
- // Recursively find the presented EasyPayViewController (if any)
752
- static func findEasyPayViewController(from vc: UIViewController?) -> EasyPayViewController? {
753
- if let easyPayVC = vc as? EasyPayViewController {
754
- return easyPayVC
755
- }
756
- if let presented = vc?.presentedViewController {
757
- return findEasyPayViewController(from: presented)
758
- }
759
- return nil
760
- }
761
- }
924
+
925
+ //extension UIApplication {
926
+ // static func topViewController(base: UIViewController? = UIApplication.shared.connectedScenes
927
+ // .compactMap { ($0 as? UIWindowScene)?.windows.first(where: \.isKeyWindow) }
928
+ // .first?.rootViewController) -> UIViewController? {
929
+ // if let nav = base as? UINavigationController {
930
+ // return topViewController(base: nav.visibleViewController)
931
+ // }
932
+ // if let tab = base as? UITabBarController {
933
+ // if let selected = tab.selectedViewController {
934
+ // return topViewController(base: selected)
935
+ // }
936
+ // }
937
+ // if let presented = base?.presentedViewController {
938
+ // return topViewController(base: presented)
939
+ // }
940
+ // return base
941
+ // }
942
+ //}
943
+
944
+ //extension UIApplication {
945
+ // // Recursively find the presented EasyPayViewController (if any)
946
+ // static func findEasyPayViewController(from vc: UIViewController?) -> EasyPayViewController? {
947
+ // if let easyPayVC = vc as? EasyPayViewController {
948
+ // return easyPayVC
949
+ // }
950
+ // if let presented = vc?.presentedViewController {
951
+ // return findEasyPayViewController(from: presented)
952
+ // }
953
+ // return nil
954
+ // }
955
+ //}
762
956