@jimrising/easymerchantsdk-react-native 1.3.9 → 1.4.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.
Files changed (80) hide show
  1. package/.idea/caches/deviceStreaming.xml +77 -0
  2. package/README.md +140 -81
  3. package/android/.gradle/8.10/checksums/checksums.lock +0 -0
  4. package/android/.gradle/8.10/checksums/md5-checksums.bin +0 -0
  5. package/android/.gradle/8.10/checksums/sha1-checksums.bin +0 -0
  6. package/android/.gradle/8.10/fileHashes/fileHashes.lock +0 -0
  7. package/android/.gradle/8.9/checksums/checksums.lock +0 -0
  8. package/android/.gradle/8.9/checksums/md5-checksums.bin +0 -0
  9. package/android/.gradle/8.9/checksums/sha1-checksums.bin +0 -0
  10. package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
  11. package/android/.gradle/buildOutputCleanup/cache.properties +1 -1
  12. package/android/build/.transforms/20e1216b87bd06eaab3c9e5c68d4267a/results.bin +1 -0
  13. package/android/build/.transforms/20e1216b87bd06eaab3c9e5c68d4267a/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/reactlibrary/BuildConfig.dex +0 -0
  14. package/android/build/.transforms/20e1216b87bd06eaab3c9e5c68d4267a/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/reactlibrary/RNEasymerchantsdkModule$1.dex +0 -0
  15. package/android/build/.transforms/20e1216b87bd06eaab3c9e5c68d4267a/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/reactlibrary/RNEasymerchantsdkModule$2.dex +0 -0
  16. package/android/build/.transforms/20e1216b87bd06eaab3c9e5c68d4267a/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/reactlibrary/RNEasymerchantsdkModule.dex +0 -0
  17. package/android/build/.transforms/20e1216b87bd06eaab3c9e5c68d4267a/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/reactlibrary/RNEasymerchantsdkPackage.dex +0 -0
  18. package/android/build/.transforms/20e1216b87bd06eaab3c9e5c68d4267a/transformed/bundleLibRuntimeToDirDebug/desugar_graph.bin +0 -0
  19. package/android/build/.transforms/e9a664a11ce12edf79cd87b1e07aa243/results.bin +1 -0
  20. package/android/build/.transforms/e9a664a11ce12edf79cd87b1e07aa243/transformed/classes/classes_dex/classes.dex +0 -0
  21. package/android/build/generated/source/buildConfig/debug/com/reactlibrary/BuildConfig.java +10 -0
  22. package/android/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/AndroidManifest.xml +7 -0
  23. package/android/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/output-metadata.json +18 -0
  24. package/android/build/intermediates/aar_metadata/debug/writeDebugAarMetadata/aar-metadata.properties +6 -0
  25. package/android/build/intermediates/annotation_processor_list/debug/javaPreCompileDebug/annotationProcessors.json +1 -0
  26. package/android/build/intermediates/compile_library_classes_jar/debug/bundleLibCompileToJarDebug/classes.jar +0 -0
  27. package/android/build/intermediates/compile_r_class_jar/debug/generateDebugRFile/R.jar +0 -0
  28. package/android/build/intermediates/compile_symbol_list/debug/generateDebugRFile/R.txt +0 -0
  29. package/android/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties +1 -0
  30. package/android/build/intermediates/incremental/debug/packageDebugResources/merger.xml +2 -0
  31. package/android/build/intermediates/incremental/mergeDebugJniLibFolders/merger.xml +2 -0
  32. package/android/build/intermediates/incremental/mergeDebugShaders/merger.xml +2 -0
  33. package/android/build/intermediates/incremental/packageDebugAssets/merger.xml +2 -0
  34. package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/BuildConfig.class +0 -0
  35. package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkModule$1.class +0 -0
  36. package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkModule$2.class +0 -0
  37. package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkModule.class +0 -0
  38. package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkPackage.class +0 -0
  39. package/android/build/intermediates/local_only_symbol_list/debug/parseDebugLocalResources/R-def.txt +2 -0
  40. package/android/build/intermediates/manifest_merge_blame_file/debug/processDebugManifest/manifest-merger-blame-debug-report.txt +7 -0
  41. package/android/build/intermediates/merged_manifest/debug/processDebugManifest/AndroidManifest.xml +7 -0
  42. package/android/build/intermediates/navigation_json/debug/extractDeepLinksDebug/navigation.json +1 -0
  43. package/android/build/intermediates/nested_resources_validation_report/debug/generateDebugResources/nestedResourcesValidationReport.txt +1 -0
  44. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/BuildConfig.class +0 -0
  45. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/RNEasymerchantsdkModule$1.class +0 -0
  46. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/RNEasymerchantsdkModule$2.class +0 -0
  47. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/RNEasymerchantsdkModule.class +0 -0
  48. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/RNEasymerchantsdkPackage.class +0 -0
  49. package/android/build/intermediates/runtime_library_classes_jar/debug/bundleLibRuntimeToJarDebug/classes.jar +0 -0
  50. package/android/build/intermediates/symbol_list_with_package_name/debug/generateDebugRFile/package-aware-r.txt +1 -0
  51. package/android/build/outputs/logs/manifest-merger-debug-report.txt +17 -0
  52. package/android/build/tmp/compileDebugJavaWithJavac/compileTransaction/stash-dir/RNEasymerchantsdkModule$1.class.uniqueId2 +0 -0
  53. package/android/build/tmp/compileDebugJavaWithJavac/compileTransaction/stash-dir/RNEasymerchantsdkModule$2.class.uniqueId0 +0 -0
  54. package/android/build/tmp/compileDebugJavaWithJavac/compileTransaction/stash-dir/RNEasymerchantsdkModule.class.uniqueId3 +0 -0
  55. package/android/build/tmp/compileDebugJavaWithJavac/compileTransaction/stash-dir/RNEasymerchantsdkPackage.class.uniqueId1 +0 -0
  56. package/android/build/tmp/compileDebugJavaWithJavac/previous-compilation-data.bin +0 -0
  57. package/android/build.gradle +4 -1
  58. package/android/src/main/java/com/reactlibrary/RNEasymerchantsdkModule.java +158 -36
  59. package/android/src/main/java/com/reactlibrary/RNEasymerchantsdkPackage.java +45 -13
  60. package/ios/Classes/EasyMerchantSdk.m +106 -55
  61. package/ios/Classes/EasyMerchantSdk.swift +199 -77
  62. package/ios/Classes/EasyPayViewController.swift +1 -1
  63. package/ios/CustomComponents/DatePickerHandler.swift +15 -4
  64. package/ios/EnvironmentConfig.swift +32 -30
  65. package/ios/Models/Request.swift +176 -14
  66. package/ios/Models/Result.swift +12 -5
  67. package/ios/Pods/ViewControllers/AdditionalInfoVC.swift +855 -366
  68. package/ios/Pods/ViewControllers/BaseVC.swift +51 -36
  69. package/ios/Pods/ViewControllers/BillingInfoVC/BillingInfoVC.swift +1985 -178
  70. package/ios/Pods/ViewControllers/CountryListVC.swift +20 -1
  71. package/ios/Pods/ViewControllers/CustomOverlay.swift +199 -0
  72. package/ios/Pods/ViewControllers/EmailVerificationVC.swift +74 -5
  73. package/ios/Pods/ViewControllers/GrailPayVC.swift +131 -107
  74. package/ios/Pods/ViewControllers/OTPVerificationVC.swift +296 -106
  75. package/ios/Pods/ViewControllers/PaymentDoneVC.swift +35 -26
  76. package/ios/Pods/ViewControllers/PaymentInformation/PaymentInfoVC.swift +1276 -545
  77. package/ios/Pods/ViewControllers/ThreeDSecurePaymentDoneVC.swift +607 -24
  78. package/ios/easymerchantsdk.podspec +1 -1
  79. package/ios/easymerchantsdk.storyboard +1388 -1165
  80. package/package.json +1 -1
@@ -1,11 +1,7 @@
1
- //
2
- // EasyMerchantSdkPlugin.swift
3
- // EasyMerchantSdk
4
- //
5
-
6
1
  import Foundation
7
2
  import UIKit
8
3
  import React
4
+ //import EasyPay // Import the EasyPay module
9
5
 
10
6
  @objc(EasyMerchantSdkPlugin)
11
7
  public class EasyMerchantSdkPlugin: NSObject, RCTBridgeModule {
@@ -14,6 +10,7 @@ public class EasyMerchantSdkPlugin: NSObject, RCTBridgeModule {
14
10
  public static func moduleName() -> String! {
15
11
  return "EasyMerchantSdk"
16
12
  }
13
+
17
14
  public static func requiresMainQueueSetup() -> Bool {
18
15
  return true
19
16
  }
@@ -23,6 +20,8 @@ public class EasyMerchantSdkPlugin: NSObject, RCTBridgeModule {
23
20
  // MARK: - Stored Promise Callbacks
24
21
  private var billingResolver: RCTPromiseResolveBlock?
25
22
  private var billingRejecter: RCTPromiseRejectBlock?
23
+ private var paymentReferenceResolver: RCTPromiseResolveBlock?
24
+ private var paymentReferenceRejecter: RCTPromiseRejectBlock?
26
25
 
27
26
  // MARK: - View Controller Reference
28
27
  private let viewControllerQueue = DispatchQueue(label: "com.easymerchantsdk.viewController")
@@ -46,12 +45,23 @@ public class EasyMerchantSdkPlugin: NSObject, RCTBridgeModule {
46
45
  }
47
46
  }
48
47
 
48
+ // MARK: - configureEnvironment(...) Exposed to RN
49
+ @objc public func configureEnvironment(_ env: String, apiKey: String, apiSecret: String) {
50
+ switch env.lowercased() {
51
+ case "production": EnvironmentConfig.setEnvironment(.production)
52
+ case "sandbox": EnvironmentConfig.setEnvironment(.sandbox)
53
+ default: EnvironmentConfig.setEnvironment(.staging)
54
+ }
55
+ EnvironmentConfig.configure(apiKey: apiKey, apiSecret: apiSecret)
56
+ }
57
+
49
58
  // MARK: - billing(...) Exposed to RN
50
59
  @objc public func billing(
51
60
  _ amount: String,
52
- billinginfo: String?,
61
+ currency: String?,
62
+ billingInfo: [String: Any]?,
53
63
  paymentMethods: [String],
54
- themeConfiguration: [String: Any],
64
+ themeConfiguration: [String: Any]?,
55
65
  tokenOnly: Bool,
56
66
  saveCard: Bool,
57
67
  saveAccount: Bool,
@@ -62,22 +72,66 @@ public class EasyMerchantSdkPlugin: NSObject, RCTBridgeModule {
62
72
  recurringIntervals: [String]?,
63
73
  recurringStartDateType: String?,
64
74
  recurringStartDate: String?,
75
+ secureAuthentication: Bool,
76
+ showReceipt: Bool,
77
+ showTotal: Bool,
78
+ showSubmitButton: Bool,
65
79
  enable3DS: Bool,
66
80
  resolver: @escaping RCTPromiseResolveBlock,
67
81
  rejecter: @escaping RCTPromiseRejectBlock
68
82
  ) {
69
83
  // 1) Validate amount
70
- guard let amountValue = Double(amount), amountValue > 0 else {
71
- rejecter("INVALID_AMOUNT", "Amount must be a positive number", nil)
84
+ guard let amountValue = Double(amount), amountValue >= 0.50 else {
85
+ rejecter("INVALID_AMOUNT", "Amount must be at least $0.50", nil)
72
86
  return
73
87
  }
74
88
 
75
- // 2) Validate billinginfo JSON
76
- if let info = billinginfo {
77
- guard info.data(using: .utf8)
78
- .flatMap({ try? JSONSerialization.jsonObject(with: $0) }) != nil
79
- else {
80
- rejecter("INVALID_BILLING_INFO", "Billing info must be valid JSON", nil)
89
+ // 2) Process billing info into FieldSection
90
+ var billingInfoData: Data? = nil
91
+ if let billingDict = billingInfo {
92
+ var billingFields: [FieldItem] = []
93
+ var additionalFields: [FieldItem] = []
94
+ var billingVisibility = true
95
+ var additionalVisibility = true
96
+
97
+ // Parse billing fields
98
+ if let billing = billingDict["billing"] as? [String: Any] {
99
+ for (key, value) in billing {
100
+ guard let fieldValue = value as? String else { continue }
101
+ let required = (billingDict["billingRequired"] as? [String: Bool])?[key] ?? false
102
+ if let billingField = BillingFieldName(rawValue: key) {
103
+ billingFields.append(FieldItem(name: billingField, required: required, value: fieldValue))
104
+ }
105
+ }
106
+ }
107
+
108
+ // Parse additional fields
109
+ if let additional = billingDict["additional"] as? [String: Any] {
110
+ for (key, value) in additional {
111
+ guard let fieldValue = value as? String else { continue }
112
+ let required = (billingDict["additionalRequired"] as? [String: Bool])?[key] ?? false
113
+ if let additionalField = AdditionalFieldName(rawValue: key) {
114
+ additionalFields.append(FieldItem(name: additionalField, required: required, value: fieldValue))
115
+ }
116
+ }
117
+ }
118
+
119
+ // Parse visibility
120
+ if let visibility = billingDict["visibility"] as? [String: Bool] {
121
+ billingVisibility = visibility["billing"] ?? true
122
+ additionalVisibility = visibility["additional"] ?? true
123
+ }
124
+
125
+ let fields = FieldSection(
126
+ visibility: FieldsVisibility(billing: billingVisibility, additional: additionalVisibility),
127
+ billing: billingFields,
128
+ additional: additionalFields
129
+ )
130
+
131
+ do {
132
+ billingInfoData = try JSONEncoder().encode(fields)
133
+ } catch {
134
+ rejecter("INVALID_BILLING_INFO", "Failed to encode billing info: \(error.localizedDescription)", nil)
81
135
  return
82
136
  }
83
137
  }
@@ -88,6 +142,7 @@ public class EasyMerchantSdkPlugin: NSObject, RCTBridgeModule {
88
142
  case "card": return .Card
89
143
  case "bank": return .Bank
90
144
  case "crypto": return .Crypto
145
+ case "wallet": return .Wallet
91
146
  default: return nil
92
147
  }
93
148
  }
@@ -96,37 +151,39 @@ public class EasyMerchantSdkPlugin: NSObject, RCTBridgeModule {
96
151
  var grailParams: GrailPayRequest? = nil
97
152
  if authenticatedACH, let params = grailPayParams {
98
153
  grailParams = GrailPayRequest(
99
- accessToken: params["accessToken"] as? String ?? "",
100
- vendorId: params["vendorId"] as? String ?? "",
101
- role: params["role"] as? String ?? "business",
102
- timeout: params["timeout"] as? Int ?? 10,
103
- isSandbox: params["isSandbox"] as? Bool ?? true,
104
- brandingName: params["brandingName"] as? String ?? "Payments",
105
- finderSubtitle: params["finderSubtitle"] as? String ?? "Find your bank",
106
- searchPlaceholder: params["searchPlaceholder"] as? String ?? "Search"
154
+ accessToken: params["accessToken"] as? String ?? "251|uTijpDGfrS88UR2V1cZNMQ8S4hUJA0sVzsnsoUZF",
155
+ vendorId: params["vendorId"] as? String ?? "251",
156
+ role: params["role"] as? String ?? "business",
157
+ timeout: params["timeout"] as? Int ?? 10,
158
+ isSandbox: params["isSandbox"] as? Bool ?? true,
159
+ brandingName: params["brandingName"] as? String ?? "Lyfecycle Payments",
160
+ finderSubtitle: params["finderSubtitle"] as? String ?? "Search for your bank",
161
+ searchPlaceholder: params["searchPlaceholder"] as? String ?? "Enter bank name"
107
162
  )
108
163
  }
109
164
 
110
165
  // 5) Build theme config
111
166
  let themeConfig = ThemeConfiguration(
112
- bodyBackgroundColor: themeConfiguration["bodyBackgroundColor"] as? String ?? "#EBF8FF",
113
- containerBackgroundColor: themeConfiguration["containerBackgroundColor"] as? String ?? "#FFFFFF",
114
- primaryFontColor: themeConfiguration["primaryFontColor"] as? String ?? "#1E3A8A",
115
- secondaryFontColor: themeConfiguration["secondaryFontColor"] as? String ?? "#696969",
116
- primaryButtonBackgroundColor: themeConfiguration["primaryButtonBackgroundColor"] as? String ?? "#1D4ED8",
117
- primaryButtonHoverColor: themeConfiguration["primaryButtonHoverColor"] as? String ?? "#2563EB",
118
- primaryButtonFontColor: themeConfiguration["primaryButtonFontColor"] as? String ?? "#FFFFFF",
119
- secondaryButtonBackgroundColor:themeConfiguration["secondaryButtonBackgroundColor"]as? String ?? "#FFFFFF",
120
- secondaryButtonHoverColor: themeConfiguration["secondaryButtonHoverColor"] as? String ?? "#BFDBFE",
121
- secondaryButtonFontColor: themeConfiguration["secondaryButtonFontColor"] as? String ?? "#1E40AF",
122
- borderRadius: themeConfiguration["borderRadius"] as? String ?? "8",
123
- fontSize: themeConfiguration["fontSize"] as? String ?? "14"
167
+ bodyBackgroundColor: themeConfiguration?["bodyBackgroundColor"] as? String ?? "#121212",
168
+ containerBackgroundColor: themeConfiguration?["containerBackgroundColor"] as? String ?? "#1E1E1E",
169
+ primaryFontColor: themeConfiguration?["primaryFontColor"] as? String ?? "#FFFFFF",
170
+ secondaryFontColor: themeConfiguration?["secondaryFontColor"] as? String ?? "#B0B0B0",
171
+ primaryButtonBackgroundColor: themeConfiguration?["primaryButtonBackgroundColor"] as? String ?? "#2563EB",
172
+ primaryButtonHoverColor: themeConfiguration?["primaryButtonHoverColor"] as? String ?? "#1D4ED8",
173
+ primaryButtonFontColor: themeConfiguration?["primaryButtonFontColor"] as? String ?? "#FFFFFF",
174
+ secondaryButtonBackgroundColor: themeConfiguration?["secondaryButtonBackgroundColor"] as? String ?? "#374151",
175
+ secondaryButtonHoverColor: themeConfiguration?["secondaryButtonHoverColor"] as? String ?? "#4B5563",
176
+ secondaryButtonFontColor: themeConfiguration?["secondaryButtonFontColor"] as? String ?? "#E5E7EB",
177
+ borderRadius: themeConfiguration?["borderRadius"] as? String ?? "8",
178
+ fontSize: themeConfiguration?["fontSize"] as? String ?? "14",
179
+ fontWeight: themeConfiguration?["fontWeight"] as? Int ?? 500,
180
+ fontFamily: themeConfiguration?["fontFamily"] as? String ?? "\"Inter\", sans-serif"
124
181
  )
125
182
 
126
- // 6) Map recurring intervals (strip leading dots)
183
+ // 6) Map recurring intervals
127
184
  let intervals: [RecurringIntervals] = (recurringIntervals ?? []).compactMap { raw in
128
185
  let cleaned = raw.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
129
- let noDot = cleaned.hasPrefix(".") ? String(cleaned.dropFirst()) : cleaned
186
+ let noDot = cleaned.hasPrefix(".") ? String(cleaned.dropFirst()) : cleaned
130
187
  return RecurringIntervals(rawValue: noDot)
131
188
  }
132
189
 
@@ -136,61 +193,114 @@ public class EasyMerchantSdkPlugin: NSObject, RCTBridgeModule {
136
193
  }
137
194
 
138
195
  // 7) Map start date type
139
- let startType: RecurringStartDateType = {
196
+ let startType: RecurringStartDateType? = {
140
197
  switch recurringStartDateType?.lowercased() {
141
198
  case "custom": return .custom
142
199
  case "fixed": return .fixed
143
- default: return .custom
200
+ default: return nil
144
201
  }
145
202
  }()
146
203
 
147
204
  // 8) Build the request object
148
205
  let request = Request(
149
- amount: amountValue,
150
- billingInfoData: billinginfo?.data(using: .utf8) ?? Data(),
151
- paymentMethods: methods,
152
- themeConfiguration: themeConfig,
153
- tokenOnly: tokenOnly,
154
- saveCard: saveCard,
155
- saveAccount: saveAccount,
156
- submitButtonText: submitButtonText ?? "Submit",
157
- authenticatedACH: authenticatedACH,
158
- grailPayParams: grailParams,
159
- is_recurring: isRecurring,
160
- recurringIntervals: intervals,
206
+ amount: amountValue,
207
+ currency: currency ?? "usd",
208
+ billingInfoData: billingInfoData,
209
+ paymentMethods: methods.isEmpty ? [.Card, .Bank] : methods,
210
+ themeConfiguration: themeConfig,
211
+ tokenOnly: tokenOnly,
212
+ saveCard: saveCard,
213
+ saveAccount: saveAccount,
214
+ submitButtonText: submitButtonText ?? "Submit",
215
+ authenticatedACH: authenticatedACH,
216
+ grailPayParams: grailParams,
217
+ is_recurring: isRecurring,
218
+ recurringIntervals: intervals,
161
219
  recurringStartDateType: startType,
162
- recurringStartDate: recurringStartDate,
163
- enable3DS: enable3DS
220
+ recurringStartDate: recurringStartDate,
221
+ secureAuthentication: secureAuthentication,
222
+ showReceipt: showReceipt,
223
+ showTotal: showTotal,
224
+ showSubmitButton: showSubmitButton,
225
+ referenceID: enable3DS,
226
+ referenceToken: nil
164
227
  )
165
228
 
166
229
  // Store resolvers
167
230
  self.billingResolver = resolver
168
- self.billingRejecter = rejecter
231
+ self.billingRejecter = rejecter
169
232
 
170
- // Present your EasyPayViewController
171
- DispatchQueue.main.async {
172
- let vc = EasyPayViewController(request: request, delegate: self)
173
- if let host = self.viewController {
174
- host.present(vc, animated: true, completion: nil)
175
- } else if let root = UIApplication.shared.windows.first(where: { $0.isKeyWindow })?.rootViewController {
176
- root.present(vc, animated: true, completion: nil)
177
- } else {
178
- rejecter("NO_VIEW_CONTROLLER", "Cannot find a view controller to present.", nil)
233
+ // Handle tokenOnly or present EasyPayViewController
234
+ if tokenOnly {
235
+ NotificationCenter.default.addObserver(
236
+ forName: NSNotification.Name("ClientTokenReceived"),
237
+ object: nil,
238
+ queue: .main
239
+ ) { [weak self] notification in
240
+ guard let self = self else { return }
241
+ if let clientToken = notification.object as? String {
242
+ self.billingResolver?(["status": "success", "clientToken": clientToken])
243
+ } else {
244
+ self.billingRejecter?("TOKEN_ERROR", "Failed to receive client token.", nil)
245
+ }
179
246
  self.clearResolvers()
180
247
  }
248
+ } else {
249
+ DispatchQueue.main.async {
250
+ let vc = EasyPayViewController(request: request, delegate: self)
251
+ if let host = self.viewController {
252
+ host.present(vc, animated: true, completion: nil)
253
+ } else if let root = UIApplication.shared.windows.first(where: { $0.isKeyWindow })?.rootViewController {
254
+ root.present(vc, animated: true, completion: nil)
255
+ } else {
256
+ rejecter("NO_VIEW_CONTROLLER", "Cannot find a view controller to present.", nil)
257
+ self.clearResolvers()
258
+ }
259
+ }
181
260
  }
182
261
  }
183
262
 
184
- // MARK: - Environment Config & Platform
185
- @objc public func configureEnvironment(_ env: String, apiKey: String, apiSecret: String) {
186
- switch env.lowercased() {
187
- case "production": EnvironmentConfig.setEnvironment(.production)
188
- case "sandbox": EnvironmentConfig.setEnvironment(.sandbox)
189
- default: EnvironmentConfig.setEnvironment(.staging)
263
+ // MARK: - paymentReference(...) Exposed to RN
264
+ @objc public func paymentReference(
265
+ _ referenceToken: String,
266
+ resolver: @escaping RCTPromiseResolveBlock,
267
+ rejecter: @escaping RCTPromiseRejectBlock
268
+ ) {
269
+ // Validate reference token
270
+ guard !referenceToken.isEmpty else {
271
+ rejecter("INVALID_REFERENCE_TOKEN", "Reference token cannot be empty.", nil)
272
+ return
273
+ }
274
+
275
+ // Store resolvers
276
+ self.paymentReferenceResolver = resolver
277
+ self.paymentReferenceRejecter = rejecter
278
+
279
+ // Build the request object
280
+ let request = Request(
281
+ referenceID: true,
282
+ referenceToken: referenceToken
283
+ )
284
+
285
+ // Set up notification observer for ReferenceIDResponse
286
+ DispatchQueue.main.async {
287
+ NotificationCenter.default.addObserver(
288
+ forName: NSNotification.Name("ReferenceIDResponse"),
289
+ object: nil,
290
+ queue: .main
291
+ ) { [weak self] notification in
292
+ guard let self = self else { return }
293
+ if let data = notification.object as? [String: Any] {
294
+ self.paymentReferenceResolver?(data)
295
+ } else {
296
+ self.paymentReferenceRejecter?("INVALID_RESPONSE", "Invalid response data.", nil)
297
+ }
298
+ self.clearReferenceResolvers()
299
+ }
190
300
  }
191
- EnvironmentConfig.configure(apiKey: apiKey, apiSecret: apiSecret)
192
301
  }
193
302
 
303
+ // MARK: - getPlatformVersion(...) Exposed to RN
194
304
  @objc public func getPlatformVersion(
195
305
  _ resolver: RCTPromiseResolveBlock,
196
306
  rejecter: RCTPromiseRejectBlock
@@ -198,9 +308,16 @@ public class EasyMerchantSdkPlugin: NSObject, RCTBridgeModule {
198
308
  resolver("iOS \(UIDevice.current.systemVersion)")
199
309
  }
200
310
 
311
+ // MARK: - Private Helpers
201
312
  private func clearResolvers() {
202
313
  billingResolver = nil
203
- billingRejecter = nil
314
+ billingRejecter = nil
315
+ }
316
+
317
+ private func clearReferenceResolvers() {
318
+ paymentReferenceResolver = nil
319
+ paymentReferenceRejecter = nil
320
+ NotificationCenter.default.removeObserver(self, name: NSNotification.Name("ReferenceIDResponse"), object: nil)
204
321
  }
205
322
  }
206
323
 
@@ -213,13 +330,19 @@ extension EasyMerchantSdkPlugin: EasyPayViewControllerDelegate {
213
330
  self.billingResolver?(["status": "cancelled", "message": "User cancelled"])
214
331
 
215
332
  case .success:
216
- // Build a single payload for RN
217
333
  var payload: [String: Any] = ["status": "success"]
218
- if let cd = result.chargeData { payload["chargeData"] = cd }
219
- if let bi = result.billingInfo { payload["billingInfo"] = bi }
220
- if let ai = result.additionalInfo{ payload["additionalInfo"] = ai }
334
+ if let cd = result.chargeData { payload["chargeData"] = cd }
335
+ if let bi = result.billingInfo { payload["billingInfo"] = bi }
336
+ if let ai = result.additionalInfo { payload["additionalInfo"] = ai }
337
+
338
+ // Extract referenceToken for 3DS
339
+ if let additional = result.additionalInfo,
340
+ let threeDSecureStatus = additional["threeDSecureStatus"] as? [String: Any],
341
+ let data = threeDSecureStatus["data"] as? [String: Any],
342
+ let token = data["ref_token"] as? String {
343
+ payload["referenceToken"] = token
344
+ }
221
345
 
222
- // Resolve promise
223
346
  self.billingResolver?(payload)
224
347
 
225
348
  case .error:
@@ -227,7 +350,6 @@ extension EasyMerchantSdkPlugin: EasyPayViewControllerDelegate {
227
350
  self.billingRejecter?("PAYMENT_ERROR", errMsg, nil)
228
351
  }
229
352
 
230
- // Cleanup & dismiss
231
353
  self.clearResolvers()
232
354
  controller.dismiss(animated: true, completion: nil)
233
355
  }
@@ -63,7 +63,7 @@ public final class EasyPayViewController: UINavigationController {
63
63
  let vc = UIStoryboard(name: "easymerchantsdk", bundle: Bundle.easyPayBundle).instantiateViewController(withIdentifier: "PaymentInfoVC") as! PaymentInfoVC
64
64
  vc.modalPresentationStyle = .overFullScreen
65
65
  vc.configureWith(request: request, delegate: easyPayDelegate)
66
- vc.amount = Int(request.amount) // Set the amount here
66
+ vc.amount = Int(request.amount ?? 0.0) // Set the amount here
67
67
  self.navigationBar.isHidden = true
68
68
  self.setViewControllers([vc], animated: false)
69
69
  }
@@ -19,12 +19,14 @@ class DatePickerHandler: NSObject {
19
19
 
20
20
  self.targetTextField = textField
21
21
  configureDatePicker()
22
+ setupInputView(for: textField)
22
23
 
23
24
  if let date = initialDate {
24
25
  datePicker.date = date
25
26
  }
26
27
 
27
- setupInputView(for: textField)
28
+ // Add observer for when the user taps the textField
29
+ textField.addTarget(self, action: #selector(textFieldEditingDidBegin), for: .editingDidBegin)
28
30
  }
29
31
 
30
32
  private func configureDatePicker() {
@@ -32,7 +34,7 @@ class DatePickerHandler: NSObject {
32
34
  datePicker.preferredDatePickerStyle = .inline
33
35
  }
34
36
  datePicker.datePickerMode = .date
35
- datePicker.minimumDate = Date()
37
+ datePicker.minimumDate = Calendar.current.startOfDay(for: Date())
36
38
  datePicker.addTarget(self, action: #selector(dateChanged(_:)), for: .valueChanged)
37
39
  }
38
40
 
@@ -49,6 +51,17 @@ class DatePickerHandler: NSObject {
49
51
  textField.inputAccessoryView = toolbar
50
52
  }
51
53
 
54
+ @objc private func textFieldEditingDidBegin() {
55
+ // When the textField is tapped, if it has a date, set it back on the date picker
56
+ if let text = targetTextField?.text, !text.isEmpty {
57
+ let formatter = DateFormatter()
58
+ formatter.dateFormat = "dd/MM/yyyy"
59
+ if let selectedDate = formatter.date(from: text) {
60
+ datePicker.setDate(selectedDate, animated: true)
61
+ }
62
+ }
63
+ }
64
+
52
65
  @objc private func dateChanged(_ sender: UIDatePicker) {
53
66
  let formatter = DateFormatter()
54
67
  formatter.dateFormat = "dd/MM/yyyy"
@@ -66,6 +79,4 @@ class DatePickerHandler: NSObject {
66
79
  }
67
80
  targetTextField?.resignFirstResponder()
68
81
  }
69
-
70
82
  }
71
-
@@ -39,34 +39,36 @@ public class EnvironmentConfig {
39
39
  self.currentEnvironment = environment
40
40
  }
41
41
 
42
- public enum Endpoints {
43
- case paymentIntent
44
- case hostedCheckouts
45
- case emailVerification
46
- case verifyOtp
47
- case getCards
48
- case creditCharges
49
- case account
50
- case achCharge
51
- case charges
52
- case accountConnect
53
- case threeDSecure
54
-
55
- func path() -> String {
56
- switch self {
57
- case .paymentIntent: return "/api/v1/paymentintent"
58
- case .hostedCheckouts: return "/api/v1/hostedcheckouts"
59
- case .emailVerification: return "/api/v1/customer/send_otp"
60
- case .verifyOtp: return "/api/v1/customer/verify_otp"
61
- // case .getCards: return "/api/v1/customer/card"
62
- case .getCards: return "/api/v1/card"
63
- case .creditCharges: return "/api/v1/customer/charges"
64
- case .account: return "/api/v1/ach/account"
65
- case .achCharge: return "/api/v1/ach/charge"
66
- case .charges: return "/api/v1/charges"
67
- case .accountConnect: return "/api/v1/ach/account/connect"
68
- case .threeDSecure: return "/api/v1/3dsecure"
69
- }
70
- }
71
- }
42
+ public enum Endpoints {
43
+ case paymentIntent
44
+ case hostedCheckouts
45
+ case emailVerification
46
+ case verifyOtp
47
+ case getCards
48
+ case creditCharges
49
+ case account
50
+ case achCharge
51
+ case charges
52
+ case accountConnect
53
+ case threeDSecure
54
+ case threeDSecureStatus(String)
55
+
56
+ func path() -> String {
57
+ switch self {
58
+ case .paymentIntent: return "/api/v1/paymentintent"
59
+ case .hostedCheckouts: return "/api/v1/hostedcheckouts"
60
+ case .emailVerification: return "/api/v1/customer/send_otp"
61
+ case .verifyOtp: return "/api/v1/customer/verify_otp"
62
+ // case .getCards: return "/api/v1/customer/card"
63
+ case .getCards: return "/api/v1/card"
64
+ case .creditCharges: return "/api/v1/customer/charges"
65
+ case .account: return "/api/v1/ach/account"
66
+ case .achCharge: return "/api/v1/ach/charge"
67
+ case .charges: return "/api/v1/charges"
68
+ case .accountConnect: return "/api/v1/ach/account/connect"
69
+ case .threeDSecure: return "/api/v1/3dsecure"
70
+ case .threeDSecureStatus(let token): return "/api/v1/3dsecure/\(token)/status"
71
+ }
72
+ }
73
+ }
72
74
  }