@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.
- package/README.md +1 -1
- package/android/.settings/org.eclipse.buildship.core.prefs +2 -2
- package/android/build.gradle +3 -4
- package/android/config.properties +5 -0
- package/android/config.properties.example +5 -0
- package/ios/ApiManager/APIRequest.swift +0 -3
- package/ios/ApiManager/APIService.swift +0 -2
- package/ios/Classes/EasyMerchantSdk.h +0 -1
- package/ios/Classes/EasyMerchantSdk.m +54 -5
- package/ios/Classes/EasyMerchantSdk.swift +1 -15
- package/ios/Classes/EasyPayViewController.swift +1 -1
- package/ios/EnvironmentConfig.swift +0 -1
- package/ios/Example/SceneDelegate.swift +23 -1
- package/ios/Example/ViewController.swift +0 -8
- package/ios/Extensions/UIFont.swift +0 -1
- package/ios/Extensions/UIViewController+Extension.swift +0 -1
- package/ios/Helper/GrailPayHelper.swift +146 -58
- package/ios/Helper/GrailPayWebViewController.swift +416 -0
- package/ios/Helper/JavaScriptBridge.swift +312 -0
- package/ios/Helper/WebViewConfig.swift +159 -0
- package/ios/Models/Request.swift +48 -204
- package/ios/easymerchantsdk.podspec +2 -2
- package/package.json +1 -1
- package/android/build/generated/source/buildConfig/debug/com/reactlibrary/BuildConfig.java +0 -10
- package/android/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/AndroidManifest.xml +0 -7
- package/android/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/output-metadata.json +0 -18
- package/android/build/intermediates/aar_metadata/debug/writeDebugAarMetadata/aar-metadata.properties +0 -6
- package/android/build/intermediates/annotation_processor_list/debug/javaPreCompileDebug/annotationProcessors.json +0 -1
- package/android/build/intermediates/compile_library_classes_jar/debug/bundleLibCompileToJarDebug/classes.jar +0 -0
- package/android/build/intermediates/compile_r_class_jar/debug/generateDebugRFile/R.jar +0 -0
- package/android/build/intermediates/compile_symbol_list/debug/generateDebugRFile/R.txt +0 -0
- package/android/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties +0 -1
- package/android/build/intermediates/incremental/debug/packageDebugResources/merger.xml +0 -2
- package/android/build/intermediates/incremental/mergeDebugAssets/merger.xml +0 -2
- package/android/build/intermediates/incremental/mergeDebugJniLibFolders/merger.xml +0 -2
- package/android/build/intermediates/incremental/mergeDebugShaders/merger.xml +0 -2
- package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/BuildConfig.class +0 -0
- package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkModule$1$1.class +0 -0
- package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkModule$1.class +0 -0
- package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkModule$2.class +0 -0
- package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkModule$3.class +0 -0
- package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkModule.class +0 -0
- package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkPackage.class +0 -0
- package/android/build/intermediates/local_only_symbol_list/debug/parseDebugLocalResources/R-def.txt +0 -2
- package/android/build/intermediates/manifest_merge_blame_file/debug/processDebugManifest/manifest-merger-blame-debug-report.txt +0 -7
- package/android/build/intermediates/merged_manifest/debug/processDebugManifest/AndroidManifest.xml +0 -7
- package/android/build/intermediates/navigation_json/debug/extractDeepLinksDebug/navigation.json +0 -1
- package/android/build/intermediates/nested_resources_validation_report/debug/generateDebugResources/nestedResourcesValidationReport.txt +0 -1
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/BuildConfig.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/RNEasymerchantsdkModule$1$1.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/RNEasymerchantsdkModule$1.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/RNEasymerchantsdkModule$2.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/RNEasymerchantsdkModule$3.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/RNEasymerchantsdkModule.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/RNEasymerchantsdkPackage.class +0 -0
- package/android/build/intermediates/runtime_library_classes_jar/debug/bundleLibRuntimeToJarDebug/classes.jar +0 -0
- package/android/build/intermediates/symbol_list_with_package_name/debug/generateDebugRFile/package-aware-r.txt +0 -1
- package/android/build/outputs/logs/manifest-merger-debug-report.txt +0 -16
- package/android/build/tmp/compileDebugJavaWithJavac/previous-compilation-data.bin +0 -0
- package/ios/Pods/UserDefaults/UserStoreSingleton.swift +0 -425
- package/ios/Pods/ViewControllers/AdditionalInfoVC.swift +0 -2996
- package/ios/Pods/ViewControllers/BaseVC.swift +0 -142
- package/ios/Pods/ViewControllers/BillingInfoVC/BillingInfoVC.swift +0 -3807
- package/ios/Pods/ViewControllers/BillingInfoVC/Cells/CityListTVC.swift +0 -46
- package/ios/Pods/ViewControllers/BillingInfoVC/Cells/CountryListTVC.swift +0 -47
- package/ios/Pods/ViewControllers/BillingInfoVC/Cells/StateListTVC.swift +0 -46
- package/ios/Pods/ViewControllers/Clean Runner_2025-07-23T14-58-05.txt +0 -13
- package/ios/Pods/ViewControllers/CountryListVC.swift +0 -435
- package/ios/Pods/ViewControllers/EmailVerificationVC.swift +0 -300
- package/ios/Pods/ViewControllers/GrailPayVC.swift +0 -492
- package/ios/Pods/ViewControllers/OTPVerificationVC.swift +0 -2278
- package/ios/Pods/ViewControllers/PaymentDoneVC.swift +0 -287
- package/ios/Pods/ViewControllers/PaymentErrorVC.swift +0 -85
- package/ios/Pods/ViewControllers/PaymentInformation/AccountTypeTVC.swift +0 -41
- package/ios/Pods/ViewControllers/PaymentInformation/PaymentInfoVC.swift +0 -13115
- package/ios/Pods/ViewControllers/PaymentInformation/PaymentInformationCVC.swift +0 -35
- package/ios/Pods/ViewControllers/PaymentInformation/RecurringTVC.swift +0 -40
- package/ios/Pods/ViewControllers/PaymentInformation/SavedAccountsTVC/SavedAccountTVC.swift +0 -80
- package/ios/Pods/ViewControllers/PaymentInformation/SavedAccountsTVC/SavedAccountTVC.xib +0 -163
- package/ios/Pods/ViewControllers/PaymentInformation/SavedCardsTVC/SavedCardsTVC.swift +0 -81
- package/ios/Pods/ViewControllers/PaymentInformation/SavedCardsTVC/SavedCardsTVC.xib +0 -188
- package/ios/Pods/ViewControllers/PaymentStatusWebViewVC.swift +0 -167
- package/ios/Pods/ViewControllers/TermAndConditionsVC.swift +0 -63
- package/ios/Pods/ViewControllers/ThreeDSecurePaymentDoneVC.swift +0 -1254
- 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
|
+
}
|