@jimrising/easymerchantsdk-react-native 2.4.8 → 2.4.9

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 (85) hide show
  1. package/README.md +1 -1
  2. package/android/.settings/org.eclipse.buildship.core.prefs +2 -2
  3. package/android/build.gradle +3 -4
  4. package/android/config.properties +5 -0
  5. package/android/config.properties.example +5 -0
  6. package/ios/ApiManager/APIRequest.swift +0 -3
  7. package/ios/ApiManager/APIService.swift +0 -2
  8. package/ios/Classes/EasyMerchantSdk.h +0 -1
  9. package/ios/Classes/EasyMerchantSdk.m +54 -5
  10. package/ios/Classes/EasyMerchantSdk.swift +1 -15
  11. package/ios/Classes/EasyPayViewController.swift +1 -1
  12. package/ios/EnvironmentConfig.swift +0 -1
  13. package/ios/Example/SceneDelegate.swift +23 -1
  14. package/ios/Example/ViewController.swift +0 -8
  15. package/ios/Extensions/UIFont.swift +0 -1
  16. package/ios/Extensions/UIViewController+Extension.swift +0 -1
  17. package/ios/Helper/GrailPayHelper.swift +146 -58
  18. package/ios/Helper/GrailPayWebViewController.swift +416 -0
  19. package/ios/Helper/JavaScriptBridge.swift +312 -0
  20. package/ios/Helper/WebViewConfig.swift +159 -0
  21. package/ios/Models/Request.swift +48 -204
  22. package/ios/easymerchantsdk.podspec +2 -2
  23. package/package.json +1 -1
  24. package/android/build/generated/source/buildConfig/debug/com/reactlibrary/BuildConfig.java +0 -10
  25. package/android/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/AndroidManifest.xml +0 -7
  26. package/android/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/output-metadata.json +0 -18
  27. package/android/build/intermediates/aar_metadata/debug/writeDebugAarMetadata/aar-metadata.properties +0 -6
  28. package/android/build/intermediates/annotation_processor_list/debug/javaPreCompileDebug/annotationProcessors.json +0 -1
  29. package/android/build/intermediates/compile_library_classes_jar/debug/bundleLibCompileToJarDebug/classes.jar +0 -0
  30. package/android/build/intermediates/compile_r_class_jar/debug/generateDebugRFile/R.jar +0 -0
  31. package/android/build/intermediates/compile_symbol_list/debug/generateDebugRFile/R.txt +0 -0
  32. package/android/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties +0 -1
  33. package/android/build/intermediates/incremental/debug/packageDebugResources/merger.xml +0 -2
  34. package/android/build/intermediates/incremental/mergeDebugAssets/merger.xml +0 -2
  35. package/android/build/intermediates/incremental/mergeDebugJniLibFolders/merger.xml +0 -2
  36. package/android/build/intermediates/incremental/mergeDebugShaders/merger.xml +0 -2
  37. package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/BuildConfig.class +0 -0
  38. package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkModule$1$1.class +0 -0
  39. package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkModule$1.class +0 -0
  40. package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkModule$2.class +0 -0
  41. package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkModule$3.class +0 -0
  42. package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkModule.class +0 -0
  43. package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkPackage.class +0 -0
  44. package/android/build/intermediates/local_only_symbol_list/debug/parseDebugLocalResources/R-def.txt +0 -2
  45. package/android/build/intermediates/manifest_merge_blame_file/debug/processDebugManifest/manifest-merger-blame-debug-report.txt +0 -7
  46. package/android/build/intermediates/merged_manifest/debug/processDebugManifest/AndroidManifest.xml +0 -7
  47. package/android/build/intermediates/navigation_json/debug/extractDeepLinksDebug/navigation.json +0 -1
  48. package/android/build/intermediates/nested_resources_validation_report/debug/generateDebugResources/nestedResourcesValidationReport.txt +0 -1
  49. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/BuildConfig.class +0 -0
  50. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/RNEasymerchantsdkModule$1$1.class +0 -0
  51. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/RNEasymerchantsdkModule$1.class +0 -0
  52. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/RNEasymerchantsdkModule$2.class +0 -0
  53. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/RNEasymerchantsdkModule$3.class +0 -0
  54. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/RNEasymerchantsdkModule.class +0 -0
  55. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/RNEasymerchantsdkPackage.class +0 -0
  56. package/android/build/intermediates/runtime_library_classes_jar/debug/bundleLibRuntimeToJarDebug/classes.jar +0 -0
  57. package/android/build/intermediates/symbol_list_with_package_name/debug/generateDebugRFile/package-aware-r.txt +0 -1
  58. package/android/build/outputs/logs/manifest-merger-debug-report.txt +0 -16
  59. package/android/build/tmp/compileDebugJavaWithJavac/previous-compilation-data.bin +0 -0
  60. package/ios/Pods/UserDefaults/UserStoreSingleton.swift +0 -425
  61. package/ios/Pods/ViewControllers/AdditionalInfoVC.swift +0 -2996
  62. package/ios/Pods/ViewControllers/BaseVC.swift +0 -142
  63. package/ios/Pods/ViewControllers/BillingInfoVC/BillingInfoVC.swift +0 -3807
  64. package/ios/Pods/ViewControllers/BillingInfoVC/Cells/CityListTVC.swift +0 -46
  65. package/ios/Pods/ViewControllers/BillingInfoVC/Cells/CountryListTVC.swift +0 -47
  66. package/ios/Pods/ViewControllers/BillingInfoVC/Cells/StateListTVC.swift +0 -46
  67. package/ios/Pods/ViewControllers/Clean Runner_2025-07-23T14-58-05.txt +0 -13
  68. package/ios/Pods/ViewControllers/CountryListVC.swift +0 -435
  69. package/ios/Pods/ViewControllers/EmailVerificationVC.swift +0 -300
  70. package/ios/Pods/ViewControllers/GrailPayVC.swift +0 -492
  71. package/ios/Pods/ViewControllers/OTPVerificationVC.swift +0 -2278
  72. package/ios/Pods/ViewControllers/PaymentDoneVC.swift +0 -287
  73. package/ios/Pods/ViewControllers/PaymentErrorVC.swift +0 -85
  74. package/ios/Pods/ViewControllers/PaymentInformation/AccountTypeTVC.swift +0 -41
  75. package/ios/Pods/ViewControllers/PaymentInformation/PaymentInfoVC.swift +0 -13115
  76. package/ios/Pods/ViewControllers/PaymentInformation/PaymentInformationCVC.swift +0 -35
  77. package/ios/Pods/ViewControllers/PaymentInformation/RecurringTVC.swift +0 -40
  78. package/ios/Pods/ViewControllers/PaymentInformation/SavedAccountsTVC/SavedAccountTVC.swift +0 -80
  79. package/ios/Pods/ViewControllers/PaymentInformation/SavedAccountsTVC/SavedAccountTVC.xib +0 -163
  80. package/ios/Pods/ViewControllers/PaymentInformation/SavedCardsTVC/SavedCardsTVC.swift +0 -81
  81. package/ios/Pods/ViewControllers/PaymentInformation/SavedCardsTVC/SavedCardsTVC.xib +0 -188
  82. package/ios/Pods/ViewControllers/PaymentStatusWebViewVC.swift +0 -167
  83. package/ios/Pods/ViewControllers/TermAndConditionsVC.swift +0 -63
  84. package/ios/Pods/ViewControllers/ThreeDSecurePaymentDoneVC.swift +0 -1254
  85. package/ios/easymerchantsdk.storyboard +0 -9089
@@ -0,0 +1,416 @@
1
+ //
2
+ // GrailPayWebViewController.swift
3
+ // EasyPay
4
+ //
5
+ // iOS WKWebView implementation for GrailPay with popup support
6
+ //
7
+
8
+ import UIKit
9
+ import WebKit
10
+ import Foundation
11
+
12
+ public class GrailPayWebViewController: UIViewController {
13
+ // MARK: - Properties
14
+ private var mainWebView: WKWebView!
15
+ private var popupWebView: WKWebView?
16
+ private var popupContainer: UIView?
17
+ private var progressView: UIProgressView!
18
+ private var closeButton: UIButton!
19
+
20
+ private var url: URL
21
+ private var completion: (SDKResult) -> Void
22
+ private var jsBridge: JavaScriptBridge!
23
+ private var hasInjectedMessageListener = false
24
+ private var hasProcessedOAuth = false // Track if OAuth was processed in popup
25
+ private var popupTimeoutTimer: DispatchWorkItem? // Safety timer to close popup
26
+
27
+ // MARK: - Testing Flag
28
+ /// QA mode (matches Android's WEBVIEW_LOG_ONLY_ON_CALLBACK_DEEPLINK):
29
+ /// - When true: if the WebView (main/popup) encounters a callback deep link URL
30
+ /// (expediter/expeditor or app scheme), we ONLY log it and do NOT process/inject.
31
+ /// - External deep links delivered to the app via AppDelegate/URL scheme
32
+ /// will still be processed normally.
33
+ static let WEBVIEW_LOG_ONLY_ON_CALLBACK_DEEPLINK: Bool = false
34
+
35
+ // MARK: - Initialization
36
+ public init(url: URL, completion: @escaping (SDKResult) -> Void) {
37
+ self.url = url
38
+ self.completion = completion
39
+ super.init(nibName: nil, bundle: nil)
40
+ }
41
+
42
+ required init?(coder: NSCoder) {
43
+ fatalError("init(coder:) has not been implemented")
44
+ }
45
+
46
+ // MARK: - Lifecycle
47
+ public override func viewDidLoad() {
48
+ super.viewDidLoad()
49
+ setupUI()
50
+ setupWebView()
51
+ loadGrailPaySDK()
52
+ }
53
+
54
+ public override func viewDidAppear(_ animated: Bool) {
55
+ super.viewDidAppear(animated)
56
+ WebViewConfig.enableCookies()
57
+ }
58
+
59
+ // MARK: - Setup
60
+ private func setupUI() {
61
+ view.backgroundColor = .systemBackground
62
+
63
+ closeButton = UIButton(type: .system)
64
+ closeButton.setImage(UIImage(systemName: "xmark.circle.fill"), for: .normal)
65
+ closeButton.tintColor = .systemGray
66
+ closeButton.addTarget(self, action: #selector(closeButtonTapped), for: .touchUpInside)
67
+ closeButton.translatesAutoresizingMaskIntoConstraints = false
68
+ view.addSubview(closeButton)
69
+
70
+ progressView = UIProgressView(progressViewStyle: .bar)
71
+ progressView.translatesAutoresizingMaskIntoConstraints = false
72
+ view.addSubview(progressView)
73
+
74
+ NSLayoutConstraint.activate([
75
+ closeButton.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 8),
76
+ closeButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),
77
+ closeButton.widthAnchor.constraint(equalToConstant: 32),
78
+ closeButton.heightAnchor.constraint(equalToConstant: 32),
79
+ progressView.topAnchor.constraint(equalTo: closeButton.bottomAnchor, constant: 8),
80
+ progressView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
81
+ progressView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
82
+ ])
83
+ }
84
+
85
+ private func setupWebView() {
86
+ jsBridge = JavaScriptBridge(webViewController: self)
87
+ mainWebView = WebViewConfig.createMainWebView(bridge: jsBridge)
88
+ mainWebView.navigationDelegate = self
89
+ mainWebView.uiDelegate = self
90
+ mainWebView.translatesAutoresizingMaskIntoConstraints = false
91
+
92
+ view.addSubview(mainWebView)
93
+
94
+ NSLayoutConstraint.activate([
95
+ mainWebView.topAnchor.constraint(equalTo: progressView.bottomAnchor),
96
+ mainWebView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
97
+ mainWebView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
98
+ mainWebView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
99
+ ])
100
+
101
+ mainWebView.addObserver(self, forKeyPath: #keyPath(WKWebView.estimatedProgress), options: .new, context: nil)
102
+ }
103
+
104
+ private func loadGrailPaySDK() {
105
+ var request = URLRequest(url: url)
106
+ request.cachePolicy = .reloadIgnoringLocalAndRemoteCacheData
107
+ request.setValue("no-cache", forHTTPHeaderField: "Cache-Control")
108
+ request.setValue("no-cache", forHTTPHeaderField: "Pragma")
109
+ mainWebView.load(request)
110
+ }
111
+
112
+ // MARK: - Popup Management
113
+ private func showPopup(with webView: WKWebView) {
114
+ let container = UIView()
115
+ container.backgroundColor = UIColor.black.withAlphaComponent(0.3)
116
+ container.translatesAutoresizingMaskIntoConstraints = false
117
+ view.addSubview(container)
118
+
119
+ let popupCloseButton = UIButton(type: .system)
120
+ popupCloseButton.setTitle("Close", for: .normal)
121
+ popupCloseButton.backgroundColor = .systemGray5
122
+ popupCloseButton.layer.cornerRadius = 8
123
+ popupCloseButton.translatesAutoresizingMaskIntoConstraints = false
124
+ popupCloseButton.addTarget(self, action: #selector(closePopupButtonTapped), for: .touchUpInside)
125
+ container.addSubview(popupCloseButton)
126
+
127
+ webView.translatesAutoresizingMaskIntoConstraints = false
128
+ container.addSubview(webView)
129
+
130
+ NSLayoutConstraint.activate([
131
+ container.topAnchor.constraint(equalTo: view.topAnchor),
132
+ container.leadingAnchor.constraint(equalTo: view.leadingAnchor),
133
+ container.trailingAnchor.constraint(equalTo: view.trailingAnchor),
134
+ container.bottomAnchor.constraint(equalTo: view.bottomAnchor),
135
+ popupCloseButton.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 16),
136
+ popupCloseButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),
137
+ popupCloseButton.widthAnchor.constraint(equalToConstant: 80),
138
+ popupCloseButton.heightAnchor.constraint(equalToConstant: 40),
139
+ webView.topAnchor.constraint(equalTo: popupCloseButton.bottomAnchor, constant: 8),
140
+ webView.leadingAnchor.constraint(equalTo: container.leadingAnchor),
141
+ webView.trailingAnchor.constraint(equalTo: container.trailingAnchor),
142
+ webView.bottomAnchor.constraint(equalTo: container.bottomAnchor)
143
+ ])
144
+
145
+ popupContainer = container
146
+ popupWebView = webView
147
+
148
+ // Start safety timeout - close popup after 30 seconds max
149
+ startPopupTimeoutTimer()
150
+ }
151
+
152
+ private func startPopupTimeoutTimer() {
153
+ popupTimeoutTimer?.cancel()
154
+ let timer = DispatchWorkItem { [weak self] in
155
+ guard let self = self, self.popupWebView != nil else { return }
156
+ self.closePopup()
157
+ }
158
+ popupTimeoutTimer = timer
159
+ DispatchQueue.main.asyncAfter(deadline: .now() + 30.0, execute: timer)
160
+ }
161
+
162
+ @objc private func closePopupButtonTapped() {
163
+ closePopup()
164
+ }
165
+
166
+ func closePopupFromBridge() {
167
+ closePopup()
168
+ }
169
+
170
+ private func closePopup() {
171
+ popupTimeoutTimer?.cancel()
172
+ popupTimeoutTimer = nil
173
+ popupWebView?.stopLoading()
174
+ popupWebView?.removeFromSuperview()
175
+ popupContainer?.removeFromSuperview()
176
+ popupWebView = nil
177
+ popupContainer = nil
178
+ hasProcessedOAuth = false
179
+ }
180
+
181
+ // MARK: - Actions
182
+ @objc private func closeButtonTapped() {
183
+ closeWebView()
184
+ }
185
+
186
+ func closeWebView() {
187
+ // Clear reference in helper
188
+ GrailPayHelper.clearWebViewController()
189
+
190
+ // Just dismiss silently - don't call completion handler
191
+ dismiss(animated: true)
192
+ }
193
+
194
+ func showAlert(title: String, message: String, onDismiss: (() -> Void)? = nil) {
195
+ let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
196
+ alert.addAction(UIAlertAction(title: "OK", style: .default) { _ in
197
+ onDismiss?()
198
+ })
199
+ present(alert, animated: true)
200
+ }
201
+
202
+ func forwardOAuthDataToMainWebView(_ oauthData: [String: String]) {
203
+ hasProcessedOAuth = true
204
+ mainWebView.injectOAuthData(oauthData)
205
+ closePopup()
206
+ }
207
+
208
+ // MARK: - Completion Handler
209
+ func handleCompletion(result: SDKResult) {
210
+ GrailPayHelper.clearWebViewController()
211
+ completion(result)
212
+ dismiss(animated: true)
213
+ }
214
+
215
+ // MARK: - Progress Observer
216
+ override public func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
217
+ if keyPath == "estimatedProgress" {
218
+ progressView.progress = Float(mainWebView.estimatedProgress)
219
+ progressView.isHidden = mainWebView.estimatedProgress >= 1.0
220
+ }
221
+ }
222
+
223
+ deinit {
224
+ mainWebView?.removeObserver(self, forKeyPath: #keyPath(WKWebView.estimatedProgress))
225
+ }
226
+ }
227
+
228
+ // MARK: - WKNavigationDelegate
229
+ extension GrailPayWebViewController: WKNavigationDelegate {
230
+ public func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
231
+ if webView == mainWebView && !hasInjectedMessageListener {
232
+ mainWebView.injectMessageListener()
233
+ hasInjectedMessageListener = true
234
+ injectConsoleLogging()
235
+ }
236
+ if webView == popupWebView {
237
+ handlePopupPageLoad(webView)
238
+ }
239
+ }
240
+
241
+ private func injectConsoleLogging() {
242
+ let script = """
243
+ (function() {
244
+ const originalError = console.error;
245
+ console.error = function(...args) {
246
+ originalError.apply(console, args);
247
+ try {
248
+ var msg = args.map(a => typeof a === 'object' ? JSON.stringify(a) : String(a)).join(' ');
249
+ if (window.webkit?.messageHandlers?.androidBridge) {
250
+ window.webkit.messageHandlers.androidBridge.postMessage({ type: 'consoleLog', level: 'error', message: msg });
251
+ }
252
+ } catch(e) {}
253
+ };
254
+ window.addEventListener('error', function(e) { console.error('Error:', e.message); });
255
+ window.addEventListener('unhandledrejection', function(e) { console.error('Rejection:', e.reason); });
256
+ })();
257
+ """
258
+ mainWebView.injectJavaScript(script)
259
+ }
260
+
261
+ private func handlePopupPageLoad(_ webView: WKWebView) {
262
+ let urlString = webView.url?.absoluteString ?? ""
263
+ if urlString == "about:blank" || urlString.isEmpty {
264
+ closePopup()
265
+ return
266
+ }
267
+ if hasProcessedOAuth {
268
+ closePopup()
269
+ return
270
+ }
271
+ if urlString.contains("auth/callback") || urlString.contains("oauth/capture") ||
272
+ urlString.contains("oauth-callback") || urlString.contains("callback?") {
273
+ hasProcessedOAuth = true
274
+ if let url = webView.url,
275
+ let components = URLComponents(url: url, resolvingAgainstBaseURL: false) {
276
+ var oauthData: [String: String] = [:]
277
+ components.queryItems?.forEach { item in
278
+ if let value = item.value { oauthData[item.name] = value }
279
+ }
280
+ oauthData["url"] = urlString
281
+ mainWebView.injectOAuthData(oauthData)
282
+ closePopup()
283
+ }
284
+ return
285
+ }
286
+ if urlString.contains("connect.moneykit.com/continue") {
287
+ hasProcessedOAuth = true
288
+ closePopup()
289
+ return
290
+ }
291
+ if urlString.contains("/success") || urlString.contains("/complete") ||
292
+ urlString.contains("/done") || urlString.contains("/finish") {
293
+ closePopup()
294
+ }
295
+ }
296
+
297
+ public func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
298
+ if webView == popupWebView { closePopup() }
299
+ }
300
+
301
+ public func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
302
+ let nsError = error as NSError
303
+ if nsError.domain == "WebKitErrorDomain" && nsError.code == 102 { return }
304
+ if webView == popupWebView { closePopup() }
305
+ }
306
+
307
+ public func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
308
+ guard let url = navigationAction.request.url else {
309
+ decisionHandler(.allow)
310
+ return
311
+ }
312
+ let urlString = url.absoluteString
313
+ let scheme = url.scheme?.lowercased() ?? ""
314
+
315
+ if urlString.lowercased().hasPrefix("grailpay://") || scheme == "grailpay" {
316
+ guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false),
317
+ let dataParam = components.queryItems?.first(where: { $0.name == "data" })?.value,
318
+ let urlDecoded = dataParam.removingPercentEncoding,
319
+ let decodedData = Data(base64Encoded: urlDecoded),
320
+ let decodedString = String(data: decodedData, encoding: .utf8),
321
+ let jsonData = decodedString.data(using: .utf8),
322
+ let json = try? JSONSerialization.jsonObject(with: jsonData) as? [String: Any],
323
+ let eventType = json["eventType"] as? String,
324
+ let data = json["data"] else {
325
+ decisionHandler(.cancel)
326
+ return
327
+ }
328
+ let result: SDKResult
329
+ switch eventType {
330
+ case "defaultAccountSelected", "linkedDefaultAccount":
331
+ result = SDKResult(type: .success, data: ["data": [data]] as NSDictionary)
332
+ case "linkExit":
333
+ if let exitData = data as? [String: Any], let status = exitData["status"] as? String, status == "SUCCESS" {
334
+ result = SDKResult(type: .success, data: ["data": [data]] as NSDictionary)
335
+ } else {
336
+ result = SDKResult(type: .cancelled, data: ["data": data] as NSDictionary)
337
+ }
338
+ case "error":
339
+ result = SDKResult(type: .error, data: ["status": false, "message": "GrailPay error: \(data)"] as NSDictionary)
340
+ default:
341
+ result = SDKResult(type: .error, data: ["status": false, "message": "Unknown event type: \(eventType)"] as NSDictionary)
342
+ }
343
+ handleCompletion(result: result)
344
+ decisionHandler(.cancel)
345
+ return
346
+ }
347
+
348
+ if scheme == "expediter" || scheme == "expeditor" {
349
+ if Self.WEBVIEW_LOG_ONLY_ON_CALLBACK_DEEPLINK {
350
+ decisionHandler(.cancel)
351
+ return
352
+ }
353
+ hasProcessedOAuth = true
354
+ var oauthData: [String: String] = [:]
355
+ URLComponents(url: url, resolvingAgainstBaseURL: false)?.queryItems?.forEach { item in
356
+ if let value = item.value { oauthData[item.name] = value }
357
+ }
358
+ oauthData["url"] = urlString
359
+ mainWebView.injectOAuthData(oauthData)
360
+ closePopup()
361
+ decisionHandler(.cancel)
362
+ return
363
+ }
364
+
365
+ if let sch = url.scheme, ["intent", "bankapp", "plaid"].contains(sch) {
366
+ if UIApplication.shared.canOpenURL(url) {
367
+ UIApplication.shared.open(url)
368
+ }
369
+ decisionHandler(.cancel)
370
+ return
371
+ }
372
+
373
+ if scheme != "http" && scheme != "https" && scheme != "about" && scheme != "data" {
374
+ decisionHandler(.cancel)
375
+ return
376
+ }
377
+ decisionHandler(.allow)
378
+ }
379
+ }
380
+
381
+ // MARK: - WKUIDelegate
382
+ extension GrailPayWebViewController: WKUIDelegate {
383
+ public func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
384
+ guard navigationAction.request.url != nil else { return nil }
385
+ let popup = WebViewConfig.createPopupWebView(with: configuration)
386
+ popup.navigationDelegate = self
387
+ popup.uiDelegate = self
388
+ showPopup(with: popup)
389
+ let script = """
390
+ (function() {
391
+ var originalClose = window.close;
392
+ window.close = function() {
393
+ try { window.webkit.messageHandlers.androidBridge.postMessage({ type: 'closePopup' }); } catch (e) {}
394
+ try { originalClose.call(window); } catch (e) {}
395
+ };
396
+ })();
397
+ """
398
+ popup.evaluateJavaScript(script, completionHandler: nil)
399
+ return popup
400
+ }
401
+
402
+ public func webViewDidClose(_ webView: WKWebView) {
403
+ if webView == popupWebView { closePopup() }
404
+ }
405
+
406
+ public func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) {
407
+ completionHandler()
408
+ }
409
+
410
+ public func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void) {
411
+ let alert = UIAlertController(title: nil, message: message, preferredStyle: .alert)
412
+ alert.addAction(UIAlertAction(title: "Cancel", style: .cancel) { _ in completionHandler(false) })
413
+ alert.addAction(UIAlertAction(title: "OK", style: .default) { _ in completionHandler(true) })
414
+ present(alert, animated: true)
415
+ }
416
+ }