@jimrising/easymerchantsdk-react-native 2.4.7 → 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/ios/easymerchantsdk.storyboard +0 -9089
|
@@ -5,40 +5,44 @@
|
|
|
5
5
|
// Created by Mony's Mac on 02/05/25.
|
|
6
6
|
//
|
|
7
7
|
|
|
8
|
-
import
|
|
8
|
+
import UIKit
|
|
9
9
|
|
|
10
10
|
@objc public class GrailPayHelper: NSObject {
|
|
11
11
|
|
|
12
|
-
private static var safariViewController: SFSafariViewController?
|
|
13
12
|
private static var completionHandler: ((SDKResult) -> Void)?
|
|
14
13
|
private static var presentingViewController: UIViewController?
|
|
14
|
+
private static var currentWebViewController: GrailPayWebViewController?
|
|
15
15
|
|
|
16
16
|
public static func presentGrailPay(
|
|
17
17
|
from viewController: UIViewController,
|
|
18
18
|
request: GrailPayRequest,
|
|
19
|
+
deepLinkScheme: String? = nil,
|
|
19
20
|
completion: @escaping (SDKResult) -> Void
|
|
20
21
|
) {
|
|
21
22
|
// Store references
|
|
22
23
|
completionHandler = completion
|
|
23
24
|
presentingViewController = viewController
|
|
24
25
|
|
|
25
|
-
//
|
|
26
|
-
|
|
26
|
+
// Get deep link scheme - use provided one or get from bundle identifier
|
|
27
|
+
let scheme = deepLinkScheme ?? Bundle.main.bundleIdentifier ?? "grailpay"
|
|
28
|
+
|
|
29
|
+
// Launch WebView (in-app browser)
|
|
30
|
+
launchWebView(from: viewController, request: request, deepLinkScheme: scheme, completion: completion)
|
|
27
31
|
}
|
|
28
32
|
|
|
29
|
-
private static func
|
|
33
|
+
private static func launchWebView(
|
|
30
34
|
from viewController: UIViewController,
|
|
31
35
|
request: GrailPayRequest,
|
|
36
|
+
deepLinkScheme: String,
|
|
32
37
|
completion: @escaping (SDKResult) -> Void
|
|
33
38
|
) {
|
|
34
|
-
print("🚀 Loading GrailPay with Safari (direct launch, no intermediate VC)")
|
|
35
39
|
|
|
36
40
|
// Build configuration JSON
|
|
37
41
|
let configDict: [String: Any] = [
|
|
38
42
|
"token": UserStoreSingleton.shared.bankWidgetKey ?? "",
|
|
39
43
|
"vendorId": UserStoreSingleton.shared.vendorID ?? "",
|
|
40
44
|
"role": request.role,
|
|
41
|
-
"timeout": request.timeout * 1000, // Convert to milliseconds
|
|
45
|
+
"timeout": max(request.timeout, 11) * 1000, // Convert to milliseconds; GrailPay minimum is 11 seconds
|
|
42
46
|
"brandingName": request.brandingName,
|
|
43
47
|
"finderSubtitle": request.finderSubtitle,
|
|
44
48
|
"searchPlaceholder": request.searchPlaceholder,
|
|
@@ -49,90 +53,187 @@ import SafariServices
|
|
|
49
53
|
guard let jsonData = try? JSONSerialization.data(withJSONObject: configDict, options: []),
|
|
50
54
|
let jsonString = String(data: jsonData, encoding: .utf8),
|
|
51
55
|
let base64Config = jsonString.data(using: .utf8)?.base64EncodedString() else {
|
|
52
|
-
print("❌ Failed to encode configuration")
|
|
53
56
|
let errorData: NSDictionary = ["status": false, "message": "Failed to encode configuration"]
|
|
54
57
|
completion(SDKResult(type: .error, data: errorData))
|
|
55
58
|
return
|
|
56
59
|
}
|
|
57
60
|
|
|
58
|
-
print("🔧 Base64 Config:", base64Config)
|
|
59
61
|
|
|
60
|
-
// Build URL with Base64 config parameter
|
|
62
|
+
// Build URL with Base64 config parameter and deep link scheme
|
|
61
63
|
let serverHTMLURL = "https://js.lyfepay.io/authenticated-ach.html"
|
|
64
|
+
let timestamp = Int(Date().timeIntervalSince1970 * 1000)
|
|
62
65
|
var components = URLComponents(string: serverHTMLURL)!
|
|
63
66
|
components.queryItems = [
|
|
64
|
-
URLQueryItem(name: "config", value: base64Config)
|
|
67
|
+
URLQueryItem(name: "config", value: base64Config),
|
|
68
|
+
URLQueryItem(name: "scheme", value: deepLinkScheme),
|
|
69
|
+
URLQueryItem(name: "v", value: "\(timestamp)")
|
|
65
70
|
]
|
|
66
71
|
|
|
67
72
|
guard let url = components.url else {
|
|
68
|
-
print("❌ Failed to build URL")
|
|
69
73
|
let errorData: NSDictionary = ["status": false, "message": "Invalid URL configuration"]
|
|
70
74
|
completion(SDKResult(type: .error, data: errorData))
|
|
71
75
|
return
|
|
72
76
|
}
|
|
73
77
|
|
|
74
|
-
print("🌐 Safari URL:", url.absoluteString)
|
|
75
78
|
|
|
76
|
-
//
|
|
77
|
-
let
|
|
78
|
-
|
|
79
|
-
|
|
79
|
+
// Create and present WebView controller
|
|
80
|
+
let webViewController = GrailPayWebViewController(url: url, completion: completion)
|
|
81
|
+
webViewController.modalPresentationStyle = .fullScreen
|
|
82
|
+
|
|
83
|
+
// Store reference to current WebView controller
|
|
84
|
+
currentWebViewController = webViewController
|
|
80
85
|
|
|
81
|
-
|
|
82
|
-
safari.dismissButtonStyle = .close
|
|
83
|
-
safari.preferredBarTintColor = .white
|
|
84
|
-
safari.preferredControlTintColor = .systemBlue
|
|
86
|
+
viewController.present(webViewController, animated: true) {
|
|
85
87
|
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/// Find the currently open GrailPayWebViewController
|
|
91
|
+
private static func findOpenWebViewController() -> GrailPayWebViewController? {
|
|
92
|
+
// First check stored reference
|
|
93
|
+
if let webVC = currentWebViewController, webVC.isViewLoaded && webVC.view.window != nil {
|
|
94
|
+
return webVC
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Search through presented view controllers
|
|
98
|
+
guard let topVC = getTopViewController() else { return nil }
|
|
86
99
|
|
|
87
|
-
//
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
100
|
+
// Check if top VC is GrailPayWebViewController
|
|
101
|
+
if let webVC = topVC as? GrailPayWebViewController {
|
|
102
|
+
return webVC
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Check presented view controller
|
|
106
|
+
if let presented = topVC.presentedViewController as? GrailPayWebViewController {
|
|
107
|
+
return presented
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Check navigation stack
|
|
111
|
+
if let navVC = topVC as? UINavigationController,
|
|
112
|
+
let webVC = navVC.topViewController as? GrailPayWebViewController {
|
|
113
|
+
return webVC
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Search through parent hierarchy
|
|
117
|
+
var currentVC: UIViewController? = topVC
|
|
118
|
+
while let vc = currentVC {
|
|
119
|
+
if let webVC = vc as? GrailPayWebViewController {
|
|
120
|
+
return webVC
|
|
121
|
+
}
|
|
122
|
+
currentVC = vc.presentedViewController ?? vc.parent
|
|
91
123
|
}
|
|
124
|
+
|
|
125
|
+
return nil
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/// Handle GrailPay OAuth callback intercepted from bank app redirect.
|
|
129
|
+
/// Note: With WebView, OAuth callbacks are handled within the WebView itself.
|
|
130
|
+
@objc(handleGrailPayOAuthCallbackWithUrl:) public static func handleGrailPayOAuthCallback(url: URL) {
|
|
131
|
+
// With WebView implementation, OAuth callbacks are automatically handled
|
|
132
|
+
// within the WebView's navigation delegate
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/// Get the top-most view controller
|
|
136
|
+
private static func getTopViewController() -> UIViewController? {
|
|
137
|
+
guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
|
|
138
|
+
let rootVC = windowScene.windows.first?.rootViewController else {
|
|
139
|
+
return nil
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
var topVC = rootVC
|
|
143
|
+
while let presentedVC = topVC.presentedViewController {
|
|
144
|
+
topVC = presentedVC
|
|
145
|
+
}
|
|
146
|
+
return topVC
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/// Clear the current WebView controller reference
|
|
150
|
+
static func clearWebViewController() {
|
|
151
|
+
currentWebViewController = nil
|
|
92
152
|
}
|
|
93
153
|
|
|
94
154
|
// Handle deep link callback
|
|
95
|
-
@objc public static func handleDeepLinkCallback(url: URL) {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
155
|
+
@objc(handleDeepLinkCallbackWithUrl:) public static func handleDeepLinkCallback(url: URL) {
|
|
156
|
+
let urlString = url.absoluteString
|
|
157
|
+
let scheme = url.scheme?.lowercased() ?? ""
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
// Handle expediter/expeditor schemes - these contain OAuth data directly in query params
|
|
161
|
+
if scheme == "expediter" || scheme == "expeditor" {
|
|
162
|
+
|
|
163
|
+
// Extract all query parameters as OAuth data
|
|
164
|
+
guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false) else {
|
|
165
|
+
return
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
var oauthData: [String: String] = [:]
|
|
169
|
+
components.queryItems?.forEach { item in
|
|
170
|
+
if let value = item.value {
|
|
171
|
+
oauthData[item.name] = value
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
oauthData["url"] = urlString
|
|
175
|
+
|
|
176
|
+
// Try to find open WebView and inject OAuth data
|
|
177
|
+
if let openWebViewController = findOpenWebViewController() {
|
|
178
|
+
DispatchQueue.main.async {
|
|
179
|
+
openWebViewController.forwardOAuthDataToMainWebView(oauthData)
|
|
180
|
+
}
|
|
181
|
+
return
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// If no WebView is open, try to use completion handler
|
|
185
|
+
if let storedCompletionHandler = completionHandler {
|
|
186
|
+
// For expediter/expeditor, we might not have eventType/data structure
|
|
187
|
+
// So we'll wrap it in a success result
|
|
188
|
+
let resultData: NSDictionary = ["data": [oauthData]]
|
|
189
|
+
let result = SDKResult(type: .success, data: resultData)
|
|
190
|
+
completionHandler = nil
|
|
191
|
+
storedCompletionHandler(result)
|
|
192
|
+
} else {
|
|
193
|
+
}
|
|
194
|
+
return
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Handle grailpay:// scheme with base64 encoded data
|
|
198
|
+
guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false) else {
|
|
199
|
+
return
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
guard let dataParam = components.queryItems?.first(where: { $0.name == "data" })?.value else {
|
|
203
|
+
if let queryItems = components.queryItems {
|
|
204
|
+
let itemNames = queryItems.map { $0.name }
|
|
205
|
+
} else {
|
|
206
|
+
}
|
|
99
207
|
return
|
|
100
208
|
}
|
|
101
209
|
|
|
102
|
-
print("🔗 Deep link received:", url.absoluteString)
|
|
103
|
-
print("📦 Data param:", dataParam)
|
|
104
210
|
|
|
105
211
|
// URL decode
|
|
106
212
|
guard let urlDecoded = dataParam.removingPercentEncoding else {
|
|
107
|
-
print("❌ Failed to URL decode")
|
|
108
213
|
return
|
|
109
214
|
}
|
|
110
215
|
|
|
111
216
|
// Base64 decode
|
|
112
217
|
guard let decodedData = Data(base64Encoded: urlDecoded),
|
|
113
218
|
let decodedString = String(data: decodedData, encoding: .utf8) else {
|
|
114
|
-
print("❌ Failed to Base64 decode")
|
|
115
219
|
return
|
|
116
220
|
}
|
|
117
221
|
|
|
118
|
-
print("📋 Decoded JSON:", decodedString)
|
|
119
222
|
|
|
120
223
|
// Parse JSON
|
|
121
224
|
guard let jsonData = decodedString.data(using: .utf8),
|
|
122
225
|
let json = try? JSONSerialization.jsonObject(with: jsonData) as? [String: Any],
|
|
123
226
|
let eventType = json["eventType"] as? String,
|
|
124
227
|
let data = json["data"] else {
|
|
125
|
-
print("❌ Failed to parse JSON")
|
|
126
228
|
return
|
|
127
229
|
}
|
|
128
230
|
|
|
129
|
-
print("✅ Event type:", eventType)
|
|
130
|
-
print("📊 Data:", data)
|
|
131
231
|
|
|
132
|
-
//
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
232
|
+
// Store reference before clearing
|
|
233
|
+
let storedCompletionHandler = completionHandler
|
|
234
|
+
|
|
235
|
+
// Clear static reference immediately
|
|
236
|
+
completionHandler = nil
|
|
136
237
|
|
|
137
238
|
// Handle callback based on event type
|
|
138
239
|
switch eventType {
|
|
@@ -140,7 +241,7 @@ import SafariServices
|
|
|
140
241
|
// Wrap account data in an array to match expected format
|
|
141
242
|
let resultData: NSDictionary = ["data": [data]]
|
|
142
243
|
let result = SDKResult(type: .success, data: resultData)
|
|
143
|
-
|
|
244
|
+
storedCompletionHandler?(result)
|
|
144
245
|
|
|
145
246
|
case "linkExit":
|
|
146
247
|
if let exitData = data as? [String: Any],
|
|
@@ -149,32 +250,19 @@ import SafariServices
|
|
|
149
250
|
// Wrap account data in an array to match expected format
|
|
150
251
|
let resultData: NSDictionary = ["data": [data]]
|
|
151
252
|
let result = SDKResult(type: .success, data: resultData)
|
|
152
|
-
|
|
253
|
+
storedCompletionHandler?(result)
|
|
153
254
|
} else {
|
|
154
255
|
let resultData: NSDictionary = ["data": data]
|
|
155
256
|
let result = SDKResult(type: .cancelled, data: resultData)
|
|
156
|
-
|
|
257
|
+
storedCompletionHandler?(result)
|
|
157
258
|
}
|
|
158
259
|
|
|
159
260
|
case "error":
|
|
160
261
|
let errorData: NSDictionary = ["status": false, "message": "GrailPay error: \(data)"]
|
|
161
262
|
let result = SDKResult(type: .error, data: errorData)
|
|
162
|
-
|
|
263
|
+
storedCompletionHandler?(result)
|
|
163
264
|
|
|
164
265
|
default:
|
|
165
|
-
print("⚠️ Unknown event type:", eventType)
|
|
166
266
|
}
|
|
167
267
|
}
|
|
168
268
|
}
|
|
169
|
-
|
|
170
|
-
// MARK: - Safari Delegate Handler
|
|
171
|
-
private class SafariDelegateHandler: NSObject, SFSafariViewControllerDelegate {
|
|
172
|
-
static let shared = SafariDelegateHandler()
|
|
173
|
-
var completion: ((SDKResult) -> Void)?
|
|
174
|
-
|
|
175
|
-
func safariViewControllerDidFinish(_ controller: SFSafariViewController) {
|
|
176
|
-
print("🚪 Safari dismissed by user (cancel)")
|
|
177
|
-
// Only call completion if no deep link was received
|
|
178
|
-
// (deep link handler will call completion with success)
|
|
179
|
-
}
|
|
180
|
-
}
|