react-native-hyperswitch-dev-sdk 0.4.2 → 0.4.4
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/HyperswitchSdkReactNative.podspec +4 -0
- package/android/build.gradle +0 -6
- package/android/hyperswitch_autolinking.gradle +1 -1
- package/ios/Modules/ReactNative/HyperModule.h +25 -0
- package/ios/Modules/ReactNative/HyperModule.mm +169 -0
- package/ios/Modules/ReactNative/HyperswitchModule.swift +223 -165
- package/ios/Modules/ReactNative/NativeHyperswitchModule.mm +17 -12
- package/ios/Modules/ReactNative/NativeHyperswitchModuleImpl.swift +18 -7
- package/ios/Modules/ReactNative/NativePaymentElementModule.mm +122 -122
- package/ios/Views/Fabric/NativePaymentElementView.mm +8 -0
- package/ios/Views/Native/NativePaymentWidget.swift +26 -22
- package/ios/hyperswitchSDK/Core/HyperCVCWidget/CVCWidget.swift +3 -2
- package/ios/hyperswitchSDK/Core/HyperPaymentCardTextField/PaymentHandler.swift +1 -1
- package/ios/hyperswitchSDK/Core/HyperPaymentSheet/PaymentSheetView+UIKit.swift +79 -13
- package/ios/hyperswitchSDK/Core/HyperPaymentSheet/PaymentSheetView.swift +2 -2
- package/ios/hyperswitchSDK/Core/HyperPaymentWidget/PaymentWidget.swift +5 -4
- package/ios/hyperswitchSDK/Core/HyperSession/PaymentSession+UIKit.swift +14 -0
- package/ios/hyperswitchSDK/Core/NativeModule/{HyperModule.swift → HyperModuleImpl.swift} +156 -113
- package/ios/hyperswitchSDK/Core/NativeModule/RNHeadlessManager.swift +58 -22
- package/ios/hyperswitchSDK/Core/NativeModule/RNViewManager.swift +84 -29
- package/ios/hyperswitchSDK/Core/NativeModule/hyper-Bridging-Header.h +2 -0
- package/ios/hyperswitchSDK/Public/RNBridgeFactory.h +28 -0
- package/ios/hyperswitchSDK/Public/RNBridgeFactory.mm +167 -0
- package/ios/hyperswitchSDK/Shared/PaymentSheet.swift +1 -1
- package/lib/commonjs/index.bundle.js +1 -1
- package/lib/commonjs/index.bundle.js.map +1 -1
- package/lib/module/index.bundle.js +1 -1
- package/lib/module/index.bundle.js.map +1 -1
- package/package.json +6 -2
- package/src/context/PaymentSession.ts +2 -0
- package/src/specs/NativeHyperswitchModule.ts +2 -2
- package/ios/Modules/ReactNative/HyperswitchSdkReactNative.h +0 -5
- package/ios/Modules/ReactNative/HyperswitchSdkReactNative.mm +0 -121
- package/ios/hyperswitchSDK/Core/NativeModule/HyperModule.m +0 -31
|
@@ -16,7 +16,7 @@ public class PaymentWidget: UIControl {
|
|
|
16
16
|
private var configurationDict: [String: Any]?
|
|
17
17
|
private var widgetReactTag: NSNumber?
|
|
18
18
|
internal var rootReactTag: NSNumber? { widgetReactTag }
|
|
19
|
-
private var rootView:
|
|
19
|
+
private var rootView: UIView?
|
|
20
20
|
private var initCallback: ((PaymentResult) -> Void)?
|
|
21
21
|
private var shouldProceedWithPaymentCallback: ((PaymentRequestData, @escaping (Bool) -> Void) -> Void)?
|
|
22
22
|
private var cancellables = Set<AnyCancellable>()
|
|
@@ -124,7 +124,7 @@ public class PaymentWidget: UIControl {
|
|
|
124
124
|
.sink { [weak self] in
|
|
125
125
|
guard let self = self else { return }
|
|
126
126
|
let payload: [String: Any] = ["rootTag": self.widgetReactTag ?? -1]
|
|
127
|
-
|
|
127
|
+
RNViewManager.sharedInstance.bridge?.enqueueJSCall(
|
|
128
128
|
"RCTDeviceEventEmitter",
|
|
129
129
|
method: "emit",
|
|
130
130
|
args: ["updateIntentInit", payload],
|
|
@@ -141,7 +141,7 @@ public class PaymentWidget: UIControl {
|
|
|
141
141
|
"rootTag": self.widgetReactTag ?? -1,
|
|
142
142
|
"sdkAuthorization": sdkAuthorization,
|
|
143
143
|
]
|
|
144
|
-
|
|
144
|
+
RNViewManager.sharedInstance.bridge?.enqueueJSCall(
|
|
145
145
|
"RCTDeviceEventEmitter",
|
|
146
146
|
method: "emit",
|
|
147
147
|
args: ["updateIntentComplete", payload],
|
|
@@ -156,7 +156,7 @@ public class PaymentWidget: UIControl {
|
|
|
156
156
|
"rootTag": self.widgetReactTag ?? -1,
|
|
157
157
|
"actionType": "CONFIRM_PAYMENT_ACTION",
|
|
158
158
|
]
|
|
159
|
-
|
|
159
|
+
RNViewManager.sharedInstance.bridge?.enqueueJSCall(
|
|
160
160
|
"RCTDeviceEventEmitter",
|
|
161
161
|
method: "emit",
|
|
162
162
|
args: ["triggerWidgetAction", payload],
|
|
@@ -210,3 +210,4 @@ public class PaymentWidget: UIControl {
|
|
|
210
210
|
}
|
|
211
211
|
}
|
|
212
212
|
}
|
|
213
|
+
|
|
@@ -73,6 +73,20 @@ extension PaymentSession {
|
|
|
73
73
|
paymentSheet.presentWithParams(from: viewController, props: params, completion: completion)
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
/// React Native / TurboModule overload — resolves with the raw JSON string from the JS
|
|
77
|
+
/// bundle so the result reaches the promise without any re-serialisation.
|
|
78
|
+
internal func presentPaymentSheetWithParams(
|
|
79
|
+
viewController: UIViewController,
|
|
80
|
+
params: [String: Any],
|
|
81
|
+
rawCompletion: @escaping (String) -> Void
|
|
82
|
+
) {
|
|
83
|
+
let paymentSheet = PaymentSheet(
|
|
84
|
+
paymentSessionConfiguration: paymentSessionConfiguration,
|
|
85
|
+
hyperswitchConfiguration: hyperswitchConfiguration ?? nil
|
|
86
|
+
)
|
|
87
|
+
paymentSheet.presentWithParams(from: viewController, props: params, rawCompletion: rawCompletion)
|
|
88
|
+
}
|
|
89
|
+
|
|
76
90
|
public func getCustomerSavedPaymentMethods(
|
|
77
91
|
_ func_: @escaping (PaymentSessionHandler) -> Void,
|
|
78
92
|
configuration: SavedPaymentMethodsConfiguration? = nil
|
|
@@ -1,90 +1,187 @@
|
|
|
1
1
|
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
2
|
+
// HyperModuleImpl.swift
|
|
3
|
+
// Pods
|
|
4
4
|
//
|
|
5
|
-
// Created by
|
|
5
|
+
// Created by Kuntimaddi Manideep on 22/07/26.
|
|
6
6
|
//
|
|
7
7
|
|
|
8
8
|
import Foundation
|
|
9
9
|
import React
|
|
10
10
|
|
|
11
|
-
@objc(
|
|
12
|
-
|
|
11
|
+
@objc(HyperModuleImpl)
|
|
12
|
+
public class HyperModuleImpl: NSObject {
|
|
13
|
+
|
|
14
|
+
@objc public static let shared = HyperModuleImpl()
|
|
15
|
+
|
|
16
|
+
/// The RN-registered event emitter (the ObjC++ `HyperModule` TurboModule) through
|
|
17
|
+
/// which JS-facing events are dispatched and the bridge / uiManager is reached.
|
|
18
|
+
@objc public weak var eventEmitter: RCTEventEmitter?
|
|
13
19
|
|
|
14
20
|
private let applePayPaymentHandler = ApplePayHandler()
|
|
15
21
|
private let expressCheckoutHandler = ExpressCheckoutLauncher()
|
|
16
22
|
private var presentCallback: RCTResponseSenderBlock? = nil
|
|
17
|
-
internal static var shared: HyperModule?
|
|
18
23
|
|
|
19
|
-
|
|
24
|
+
/// Direct reference to the currently-presented PaymentSheet.
|
|
25
|
+
/// Set by PaymentSheetView+UIKit when the sheet is presented; cleared in exitPaymentsheet.
|
|
26
|
+
/// Avoids relying on bridge.uiManager.addUIBlock (which doesn't find Fabric views).
|
|
27
|
+
private var activePaymentSheet: PaymentSheet?
|
|
28
|
+
private weak var activePaymentSheetVC: HyperUIViewController?
|
|
29
|
+
|
|
30
|
+
private override init() {
|
|
20
31
|
super.init()
|
|
21
|
-
HyperModule.shared = self
|
|
22
32
|
}
|
|
23
33
|
|
|
24
|
-
|
|
25
|
-
internal override static func requiresMainQueueSetup() -> Bool {
|
|
26
|
-
return true
|
|
27
|
-
}
|
|
34
|
+
// MARK: - Payment sheet registration (called from PaymentSheetView+UIKit)
|
|
28
35
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
36
|
+
internal func registerPaymentSheet(_ sheet: PaymentSheet, vc: HyperUIViewController) {
|
|
37
|
+
activePaymentSheet = sheet
|
|
38
|
+
activePaymentSheetVC = vc
|
|
32
39
|
}
|
|
33
40
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
41
|
+
/// The bridge backing the RN-registered emitter (embedded runtime is bridge-based).
|
|
42
|
+
private var bridge: RCTBridge? { eventEmitter?.bridge }
|
|
43
|
+
|
|
44
|
+
// MARK: - Events
|
|
45
|
+
|
|
46
|
+
@objc public func confirm(data: [String: Any]) {
|
|
47
|
+
eventEmitter?.sendEvent(withName: "confirm", body: data)
|
|
37
48
|
}
|
|
38
49
|
// MARK: WIP
|
|
39
|
-
// @objc func confirmEC(data: [String: Any]) {
|
|
40
|
-
//
|
|
50
|
+
// @objc public func confirmEC(data: [String: Any]) {
|
|
51
|
+
// eventEmitter?.sendEvent(withName: "confirmEC", body: data)
|
|
41
52
|
// }
|
|
42
53
|
|
|
43
|
-
|
|
44
|
-
private func sendMessageToNative(_ rnMessage: String) {}
|
|
54
|
+
// MARK: - Generic message passing
|
|
45
55
|
|
|
46
|
-
@objc
|
|
47
|
-
|
|
48
|
-
|
|
56
|
+
@objc public func sendMessageToNative(_ rnMessage: String) {}
|
|
57
|
+
|
|
58
|
+
// MARK: - Apple Pay
|
|
59
|
+
|
|
60
|
+
@objc public func launchApplePay(_ rnMessage: String, callback: @escaping RCTResponseSenderBlock) {
|
|
61
|
+
applePayPaymentHandler.startPayment(rnMessage: rnMessage, rnCallback: callback, presentCallback: self.presentCallback)
|
|
49
62
|
}
|
|
50
63
|
|
|
51
|
-
@objc
|
|
52
|
-
|
|
53
|
-
PaymentMethodManagementWidget.onAddPaymentMethod?()
|
|
64
|
+
@objc public func startApplePay(_ rnMessage: String, callback: @escaping RCTResponseSenderBlock) {
|
|
65
|
+
callback([])
|
|
54
66
|
}
|
|
55
67
|
|
|
56
|
-
@objc
|
|
57
|
-
|
|
58
|
-
applePayPaymentHandler.startPayment(rnMessage: rnMessage, rnCallback: rnCallback, presentCallback: self.presentCallback)
|
|
68
|
+
@objc public func presentApplePay(_ rnMessage: String, callback: @escaping RCTResponseSenderBlock) {
|
|
69
|
+
self.presentCallback = callback
|
|
59
70
|
}
|
|
60
71
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
72
|
+
// MARK: - Widget
|
|
73
|
+
|
|
74
|
+
@objc public func launchWidgetPaymentSheet(_ requestObj: String, callback: @escaping RCTResponseSenderBlock) {
|
|
75
|
+
let request = HyperModuleImpl.jsonObject(from: requestObj)
|
|
76
|
+
expressCheckoutHandler.launchPaymentSheet(paymentResult: request, callBack: callback)
|
|
64
77
|
}
|
|
65
78
|
|
|
66
|
-
@objc
|
|
67
|
-
|
|
68
|
-
self.presentCallback = rnCallback
|
|
79
|
+
@objc public func onAddPaymentMethod(_ rnMessage: String) {
|
|
80
|
+
PaymentMethodManagementWidget.onAddPaymentMethod?()
|
|
69
81
|
}
|
|
70
82
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
83
|
+
// MARK: - Payment sheet exits
|
|
84
|
+
|
|
85
|
+
@objc public func exitPaymentsheet(_ reactTag: NSNumber, result rnMessage: String, reset: Bool) {
|
|
86
|
+
// Consume and clear so they can't be called a second time.
|
|
87
|
+
let sheet = activePaymentSheet
|
|
88
|
+
let vc = activePaymentSheetVC
|
|
89
|
+
activePaymentSheet = nil
|
|
90
|
+
activePaymentSheetVC = nil
|
|
91
|
+
|
|
92
|
+
// Pass the raw JS result string directly — no PaymentResult conversion.
|
|
93
|
+
// PaymentSheet.completion is (String) -> Void, so this goes straight to
|
|
94
|
+
// the promise resolve in HyperswitchModule.presentPaymentSheet.
|
|
95
|
+
sheet?.completion?(rnMessage)
|
|
96
|
+
|
|
97
|
+
// Dismiss the VC, then release the rootView so the Fabric surface is torn down.
|
|
98
|
+
vc?.dismiss(animated: false) {
|
|
99
|
+
RNViewManager.sharedInstance.rootView?.removeFromSuperview()
|
|
100
|
+
RNViewManager.sharedInstance.rootView = nil
|
|
77
101
|
}
|
|
78
102
|
}
|
|
79
103
|
|
|
80
|
-
@objc
|
|
81
|
-
private func exitWidgetPaymentsheet(_ reactTag: NSNumber, _ rnMessage: String, _ reset: Bool) {
|
|
104
|
+
@objc public func exitWidgetPaymentsheet(_ reactTag: NSNumber, result rnMessage: String, reset: Bool) {
|
|
82
105
|
let result = paymentResult(from: rnMessage)
|
|
83
106
|
withWidget(reactTag) { w in
|
|
84
107
|
w.handleConfirmPaymentResponse(result)
|
|
85
108
|
}
|
|
86
109
|
}
|
|
87
110
|
|
|
111
|
+
@objc public func exitPaymentMethodManagement(_ reactTag: NSNumber, result rnMessage: String, reset: Bool) {
|
|
112
|
+
exitSheet(rnMessage)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
@objc public func exitCardForm(_ rnMessage: String) {
|
|
116
|
+
exitCardFormInternal(rnMessage)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// MARK: - Payment result / events
|
|
120
|
+
|
|
121
|
+
@objc public func notifyWidgetPaymentResult(_ rootTag: NSNumber, result rnMessage: String) {
|
|
122
|
+
let result = paymentResult(from: rnMessage)
|
|
123
|
+
guard case .failed = result else { return }
|
|
124
|
+
withNativePaymentWidgetView(rootTag) { view in
|
|
125
|
+
view.handleConfirmPaymentResponse(result)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
@objc public func onUpdateIntentEvent(_ rootTag: NSNumber, type: String, result: String) {
|
|
130
|
+
withWidget(rootTag) { widget in
|
|
131
|
+
widget.handleUpdateIntentEvent(type: type, result: result)
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
@objc public func emitPaymentEvent(_ rootTag: NSNumber, eventType: String, payload: NSDictionary) {
|
|
136
|
+
let map = (payload as? [String: Any]) ?? [:]
|
|
137
|
+
resolveSubscribingTarget(rootTag) { target in
|
|
138
|
+
if let widget = target as? PaymentWidget, widget.paymentEventListener != nil {
|
|
139
|
+
widget.dispatchPaymentEvent(type: eventType, payload: map)
|
|
140
|
+
} else if let cvc = target as? CVCWidget, cvc.paymentEventListener != nil {
|
|
141
|
+
cvc.dispatchPaymentEvent(type: eventType, payload: map)
|
|
142
|
+
} else if let sheet = target as? PaymentSheet, sheet.paymentEventListener != nil {
|
|
143
|
+
sheet.dispatchPaymentEvent(type: eventType, payload: map)
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
@objc public func onPaymentConfirmButtonClick(_ rootTag: NSNumber, payload: String, callback: @escaping RCTResponseSenderBlock) {
|
|
149
|
+
resolveSubscribingTarget(rootTag) { target in
|
|
150
|
+
if let widget = target as? PaymentWidget {
|
|
151
|
+
widget.handleShouldProceedWithPayment(payload: payload) { shouldProceed in
|
|
152
|
+
callback([shouldProceed])
|
|
153
|
+
}
|
|
154
|
+
} else if let sheet = target as? PaymentSheet {
|
|
155
|
+
sheet.handleShouldProceedWithPayment(payload: payload) { shouldProceed in
|
|
156
|
+
callback([shouldProceed])
|
|
157
|
+
}
|
|
158
|
+
} else {
|
|
159
|
+
callback([true])
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// MARK: - 3DS / DDC iframe bridge
|
|
165
|
+
|
|
166
|
+
@objc public func openIframeBridge(_ url: String, timeoutMs: NSNumber, callback: @escaping RCTResponseSenderBlock) {
|
|
167
|
+
DispatchQueue.main.async {
|
|
168
|
+
let headlessWebView = HeadlessWebView(url: url, timeoutMs: timeoutMs, callback: callback)
|
|
169
|
+
headlessWebView.startFlow()
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// MARK: - Helpers
|
|
174
|
+
|
|
175
|
+
private static func jsonObject(from string: String) -> NSMutableDictionary {
|
|
176
|
+
guard
|
|
177
|
+
let data = string.data(using: .utf8),
|
|
178
|
+
let obj = try? JSONSerialization.jsonObject(with: data, options: []) as? [AnyHashable: Any]
|
|
179
|
+
else {
|
|
180
|
+
return NSMutableDictionary()
|
|
181
|
+
}
|
|
182
|
+
return NSMutableDictionary(dictionary: obj)
|
|
183
|
+
}
|
|
184
|
+
|
|
88
185
|
private func paymentResult(from rnMessage: String) -> PaymentResult {
|
|
89
186
|
guard let data = rnMessage.data(using: .utf8) else {
|
|
90
187
|
return .failed(
|
|
@@ -132,42 +229,7 @@ internal class HyperModule: RCTEventEmitter {
|
|
|
132
229
|
}
|
|
133
230
|
}
|
|
134
231
|
|
|
135
|
-
|
|
136
|
-
private func exitPaymentMethodManagement(_ reactTag: NSNumber, _ rnMessage: String, _ reset: Bool) {
|
|
137
|
-
exitSheet(rnMessage)
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
@objc
|
|
141
|
-
private func notifyWidgetPaymentResult(_ rootTag: NSNumber, _ rnMessage: String) {
|
|
142
|
-
let result = paymentResult(from: rnMessage)
|
|
143
|
-
guard case .failed = result else { return }
|
|
144
|
-
withNativePaymentWidgetView(rootTag) { view in
|
|
145
|
-
view.handleConfirmPaymentNotification(result)
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
@objc
|
|
150
|
-
private func onUpdateIntentEvent(_ rootTag: NSNumber, _ type: String, _ result: String) {
|
|
151
|
-
withWidget(rootTag) { widget in
|
|
152
|
-
widget.handleUpdateIntentEvent(type: type, result: result)
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
@objc func emitPaymentEvent(_ rootTag: NSNumber, _ eventType: String, _ payload: NSDictionary) {
|
|
157
|
-
let map = (payload as? [String: Any]) ?? [:]
|
|
158
|
-
resolveSubscribingTarget(rootTag) { target in
|
|
159
|
-
if let widget = target as? PaymentWidget, widget.paymentEventListener != nil {
|
|
160
|
-
widget.dispatchPaymentEvent(type: eventType, payload: map)
|
|
161
|
-
} else if let cvc = target as? CVCWidget, cvc.paymentEventListener != nil {
|
|
162
|
-
cvc.dispatchPaymentEvent(type: eventType, payload: map)
|
|
163
|
-
} else if let sheet = target as? PaymentSheet, sheet.paymentEventListener != nil {
|
|
164
|
-
sheet.dispatchPaymentEvent(type: eventType, payload: map)
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
@objc
|
|
170
|
-
private func exitCardForm(_ rnMessage: String) {
|
|
232
|
+
private func exitCardFormInternal(_ rnMessage: String) {
|
|
171
233
|
var response: String?
|
|
172
234
|
var error: NSError?
|
|
173
235
|
|
|
@@ -206,7 +268,6 @@ internal class HyperModule: RCTEventEmitter {
|
|
|
206
268
|
}
|
|
207
269
|
}
|
|
208
270
|
|
|
209
|
-
@objc
|
|
210
271
|
private func exitSheet(_ rnMessage: String) {
|
|
211
272
|
var response: String?
|
|
212
273
|
var error: NSError?
|
|
@@ -252,34 +313,10 @@ internal class HyperModule: RCTEventEmitter {
|
|
|
252
313
|
}
|
|
253
314
|
}
|
|
254
315
|
|
|
255
|
-
@objc
|
|
256
|
-
private func onPaymentConfirmButtonClick(_ rootTag: NSNumber, _ payload: String, _ callback: @escaping RCTResponseSenderBlock) {
|
|
257
|
-
resolveSubscribingTarget(rootTag) { target in
|
|
258
|
-
if let widget = target as? PaymentWidget {
|
|
259
|
-
widget.handleShouldProceedWithPayment(payload: payload) { shouldProceed in
|
|
260
|
-
callback([shouldProceed])
|
|
261
|
-
}
|
|
262
|
-
} else if let sheet = target as? PaymentSheet {
|
|
263
|
-
sheet.handleShouldProceedWithPayment(payload: payload) { shouldProceed in
|
|
264
|
-
callback([shouldProceed])
|
|
265
|
-
}
|
|
266
|
-
} else {
|
|
267
|
-
callback([true])
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
@objc
|
|
273
|
-
private func openIframeBridge(_ url: String, _ timeoutMs: NSNumber, _ callback: @escaping RCTResponseSenderBlock) {
|
|
274
|
-
DispatchQueue.main.async {
|
|
275
|
-
let headlessWebView = HeadlessWebView(url: url, timeoutMs: timeoutMs, callback: callback)
|
|
276
|
-
headlessWebView.startFlow()
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
|
|
280
316
|
private func withWidget(_ rootTag: NSNumber, _ block: @escaping (PaymentWidget) -> Void) {
|
|
317
|
+
guard let bridge = self.bridge else { return }
|
|
281
318
|
RCTGetUIManagerQueue().async {
|
|
282
|
-
|
|
319
|
+
bridge.uiManager.addUIBlock { _, viewRegistry in
|
|
283
320
|
guard let view = viewRegistry?[rootTag] else { return }
|
|
284
321
|
var current: UIView? = view
|
|
285
322
|
while let v = current {
|
|
@@ -293,13 +330,14 @@ internal class HyperModule: RCTEventEmitter {
|
|
|
293
330
|
}
|
|
294
331
|
}
|
|
295
332
|
|
|
296
|
-
private func withNativePaymentWidgetView(_ rootTag: NSNumber, _ block: @escaping (
|
|
333
|
+
private func withNativePaymentWidgetView(_ rootTag: NSNumber, _ block: @escaping (PaymentWidget) -> Void) {
|
|
334
|
+
guard let bridge = self.bridge else { return }
|
|
297
335
|
RCTGetUIManagerQueue().async {
|
|
298
|
-
|
|
336
|
+
bridge.uiManager.addUIBlock { _, viewRegistry in
|
|
299
337
|
guard let view = viewRegistry?[rootTag] else { return }
|
|
300
338
|
var current: UIView? = view
|
|
301
339
|
while let v = current {
|
|
302
|
-
if let nativeWidget = v as?
|
|
340
|
+
if let nativeWidget = v as? PaymentWidget {
|
|
303
341
|
block(nativeWidget)
|
|
304
342
|
return
|
|
305
343
|
}
|
|
@@ -310,8 +348,12 @@ internal class HyperModule: RCTEventEmitter {
|
|
|
310
348
|
}
|
|
311
349
|
|
|
312
350
|
private func resolveSubscribingTarget(_ rootTag: NSNumber, _ block: @escaping (AnyObject?) -> Void) {
|
|
351
|
+
guard let bridge = self.bridge else {
|
|
352
|
+
DispatchQueue.main.async { block(nil) }
|
|
353
|
+
return
|
|
354
|
+
}
|
|
313
355
|
RCTGetUIManagerQueue().async {
|
|
314
|
-
|
|
356
|
+
bridge.uiManager.addUIBlock { _, viewRegistry in
|
|
315
357
|
guard let view = viewRegistry?[rootTag] else {
|
|
316
358
|
DispatchQueue.main.async { block(nil) }
|
|
317
359
|
return
|
|
@@ -331,8 +373,9 @@ internal class HyperModule: RCTEventEmitter {
|
|
|
331
373
|
}
|
|
332
374
|
|
|
333
375
|
private func withPaymentSheet(_ rootTag: NSNumber, _ block: @escaping (UIViewController?, PaymentSheet?) -> Void) {
|
|
376
|
+
guard let bridge = self.bridge else { return }
|
|
334
377
|
RCTGetUIManagerQueue().async {
|
|
335
|
-
|
|
378
|
+
bridge.uiManager.addUIBlock { _, viewRegistry in
|
|
336
379
|
let view = viewRegistry?[rootTag]
|
|
337
380
|
let vc = view?.reactViewController() as? HyperUIViewController
|
|
338
381
|
let sheet = vc?.paymentSheet
|
|
@@ -2,46 +2,82 @@
|
|
|
2
2
|
// RNHeadlessManager.swift
|
|
3
3
|
// Hyperswitch
|
|
4
4
|
//
|
|
5
|
+
// Hosts the SDK's headless React Native runtime (the "HyperHeadless" module)
|
|
6
|
+
// that drives headless payment sessions without any visible UI.
|
|
7
|
+
//
|
|
8
|
+
// New Architecture note:
|
|
9
|
+
// ----------------------
|
|
10
|
+
// Mirrors RNViewManager: uses the full RCTReactNativeFactory rather than the
|
|
11
|
+
// legacy RCTBridge(delegate:launchOptions:) + RCTRootView(bridge:…) pair.
|
|
12
|
+
// The raw bridge init is incompatible with RCTSetNewArchEnabled(YES) (set
|
|
13
|
+
// globally by the host app's RN runtime), which caused EXC_BAD_ACCESS at
|
|
14
|
+
// offset 0x69f when accessing the bridge's C++ internals via RCTRootView.
|
|
15
|
+
//
|
|
16
|
+
// reinvalidateBridge() is preserved for callers (PaymentSession+UIKit) that
|
|
17
|
+
// need to reset the headless runtime between sessions; it now tears down and
|
|
18
|
+
// lazily recreates the RCTReactNativeFactory instead of the bare bridge.
|
|
19
|
+
//
|
|
5
20
|
// Created by Shivam Shashank on 09/11/22.
|
|
6
21
|
//
|
|
7
22
|
|
|
8
23
|
import Foundation
|
|
9
24
|
import React
|
|
25
|
+
import React_RCTAppDelegate
|
|
26
|
+
import ReactAppDependencyProvider
|
|
10
27
|
|
|
11
|
-
internal class RNHeadlessManager:
|
|
28
|
+
internal class RNHeadlessManager: RCTDefaultReactNativeFactoryDelegate {
|
|
12
29
|
|
|
13
30
|
internal var responseHandler: RNResponseHandler?
|
|
14
|
-
internal var rootView:
|
|
31
|
+
internal var rootView: UIView?
|
|
15
32
|
|
|
16
|
-
|
|
17
|
-
RCTBridge.init(delegate: self, launchOptions: nil)
|
|
18
|
-
}()
|
|
33
|
+
private var reactNativeFactory: RCTReactNativeFactory?
|
|
19
34
|
|
|
20
35
|
internal static let sharedInstance = RNHeadlessManager()
|
|
21
36
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
37
|
+
// MARK: - Factory lifecycle
|
|
38
|
+
|
|
39
|
+
/// Lazily creates the RCTReactNativeFactory (and its bridge) on first use.
|
|
40
|
+
private func factory() -> RCTReactNativeFactory {
|
|
41
|
+
if let existing = reactNativeFactory {
|
|
42
|
+
return existing
|
|
43
|
+
}
|
|
44
|
+
self.dependencyProvider = RCTAppDependencyProvider()
|
|
45
|
+
let created = RCTReactNativeFactory(delegate: self)
|
|
46
|
+
reactNativeFactory = created
|
|
47
|
+
return created
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// MARK: - View creation
|
|
51
|
+
|
|
52
|
+
internal func viewForModule(_ moduleName: String, initialProperties: [String: Any]?) -> UIView {
|
|
53
|
+
let view = factory().rootViewFactory.view(
|
|
54
|
+
withModuleName: moduleName,
|
|
26
55
|
initialProperties: initialProperties
|
|
27
56
|
)
|
|
28
|
-
self.rootView =
|
|
29
|
-
return
|
|
57
|
+
self.rootView = view
|
|
58
|
+
return view
|
|
30
59
|
}
|
|
31
60
|
|
|
61
|
+
/// Tear down the current factory (and its bridge) so the next call to
|
|
62
|
+
/// viewForModule starts a fresh JS runtime for the new headless session.
|
|
32
63
|
internal func reinvalidateBridge() {
|
|
33
|
-
|
|
34
|
-
|
|
64
|
+
reactNativeFactory = nil
|
|
65
|
+
rootView = nil
|
|
35
66
|
}
|
|
36
|
-
}
|
|
37
67
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
68
|
+
// MARK: - RCTDefaultReactNativeFactoryDelegate
|
|
69
|
+
|
|
70
|
+
/// Use bridge mode (not bridgeless) so the headless module can use the
|
|
71
|
+
/// legacy RCTBridge event / callback APIs.
|
|
72
|
+
public override func bridgelessEnabled() -> Bool {
|
|
73
|
+
return false
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
override func sourceURL(for bridge: RCTBridge) -> URL? {
|
|
77
|
+
return bundleURL()
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
public override func bundleURL() -> URL? {
|
|
81
|
+
return Bundle(for: RNHeadlessManager.self).url(forResource: "hyperswitch", withExtension: "bundle")
|
|
46
82
|
}
|
|
47
83
|
}
|
|
@@ -2,54 +2,109 @@
|
|
|
2
2
|
// RNViewManager.swift
|
|
3
3
|
// Hyperswitch
|
|
4
4
|
//
|
|
5
|
+
// Hosts the SDK's embedded React Native runtime that renders the payment sheet
|
|
6
|
+
// from the packaged `hyperswitch.bundle`.
|
|
7
|
+
//
|
|
8
|
+
// New Architecture note:
|
|
9
|
+
// ----------------------
|
|
10
|
+
// The embedded runtime MUST be created through the full `RCTReactNativeFactory`.
|
|
11
|
+
// That initializer performs the essential RN setup — feature flags,
|
|
12
|
+
// `RCTSetNewArchEnabled`, `RCTEnableTurboModule`, the Fabric component provider —
|
|
13
|
+
// and wires a TurboModule manager that can resolve RN's own CORE modules
|
|
14
|
+
// (`DeviceInfo`, `ExceptionsManager`, `RedBox`, …). Building the low-level
|
|
15
|
+
// `RCTRootViewFactory` directly (as the old `RNBridgeFactory` did) leaves the
|
|
16
|
+
// runtime under-initialised, so `getEnforcing('DeviceInfo')` fails and the bundle
|
|
17
|
+
// can't boot — the sheet renders blank.
|
|
18
|
+
//
|
|
19
|
+
// We keep the *bridge* alive by overriding `bridgelessEnabled` → false, because
|
|
20
|
+
// the SDK's widget commands still use `bridge.enqueueJSCall` / `bridge.uiManager`.
|
|
21
|
+
//
|
|
5
22
|
// Created by Shivam Shashank on 09/11/22.
|
|
6
23
|
//
|
|
7
24
|
|
|
8
25
|
import Foundation
|
|
9
26
|
import React
|
|
27
|
+
import React_RCTAppDelegate
|
|
28
|
+
import ReactAppDependencyProvider
|
|
10
29
|
|
|
11
|
-
internal class RNViewManager:
|
|
30
|
+
internal class RNViewManager: RCTDefaultReactNativeFactoryDelegate {
|
|
12
31
|
|
|
13
32
|
internal var responseHandler: RNResponseHandler?
|
|
14
|
-
internal var rootView:
|
|
33
|
+
internal var rootView: UIView?
|
|
15
34
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
35
|
+
private var reactNativeFactory: RCTReactNativeFactory?
|
|
36
|
+
|
|
37
|
+
/// Captured when the embedded bridge loads its JS source (see `sourceURL(for:)`).
|
|
38
|
+
/// The host app is bridgeless, so this embedded bridge is the SDK's only bridge.
|
|
39
|
+
private weak var capturedBridge: RCTBridge?
|
|
19
40
|
|
|
20
41
|
internal static let sharedInstance = RNViewManager()
|
|
21
42
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
43
|
+
/// Lazily create the standard RN factory (does the full runtime setup).
|
|
44
|
+
private func factory() -> RCTReactNativeFactory {
|
|
45
|
+
if let existing = reactNativeFactory {
|
|
46
|
+
return existing
|
|
47
|
+
}
|
|
48
|
+
self.dependencyProvider = RCTAppDependencyProvider()
|
|
49
|
+
let created = RCTReactNativeFactory(delegate: self)
|
|
50
|
+
reactNativeFactory = created
|
|
51
|
+
return created
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/// The bridge backing the embedded runtime. Kept for the SDK's widget commands
|
|
55
|
+
/// (`bridge.enqueueJSCall` / `bridge.uiManager`). Available after the first view
|
|
56
|
+
/// is created.
|
|
57
|
+
internal var bridge: RCTBridge? {
|
|
58
|
+
return capturedBridge
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
internal func viewForModule(_ moduleName: String, initialProperties: [String: Any]?) -> UIView {
|
|
62
|
+
let view = factory().rootViewFactory.view(
|
|
63
|
+
withModuleName: moduleName,
|
|
26
64
|
initialProperties: initialProperties
|
|
27
65
|
)
|
|
28
|
-
self.rootView =
|
|
29
|
-
return
|
|
66
|
+
self.rootView = view
|
|
67
|
+
return view
|
|
30
68
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
69
|
+
|
|
70
|
+
internal func widgetViewForModule(_ moduleName: String, initialProperties: [String: Any]?) -> UIView {
|
|
71
|
+
return factory().rootViewFactory.view(
|
|
72
|
+
withModuleName: moduleName,
|
|
35
73
|
initialProperties: initialProperties
|
|
36
74
|
)
|
|
37
75
|
}
|
|
38
|
-
}
|
|
39
76
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
77
|
+
// MARK: - Arch configuration
|
|
78
|
+
|
|
79
|
+
/// Keep the legacy bridge alive (Fabric-on-bridge) so the SDK's
|
|
80
|
+
/// `bridge.enqueueJSCall` / `bridge.uiManager` widget commands keep working.
|
|
81
|
+
public override func bridgelessEnabled() -> Bool {
|
|
82
|
+
return false
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// MARK: - Bundle source
|
|
86
|
+
// Both are required in bridge mode: the default implementations throw.
|
|
87
|
+
|
|
88
|
+
override func sourceURL(for bridge: RCTBridge) -> URL? {
|
|
89
|
+
// Called while the embedded bridge loads its JS — capture it for the SDK's
|
|
90
|
+
// bridge-dependent widget commands.
|
|
91
|
+
self.capturedBridge = bridge
|
|
92
|
+
return bundleURL()
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
public override func bundleURL() -> URL? {
|
|
96
|
+
// switch Helper.getInfoPlist("HyperswitchSource") {
|
|
97
|
+
// case "LocalHosted":
|
|
98
|
+
// return RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index")
|
|
99
|
+
// case "LocalBundle":
|
|
100
|
+
// return Bundle.main.url(forResource: "hyperswitch", withExtension: "bundle")
|
|
101
|
+
// default:
|
|
102
|
+
// #if canImport(HyperOTA)
|
|
103
|
+
// return OTAServices.shared.getBundleURL()
|
|
104
|
+
// #else
|
|
51
105
|
return Bundle(for: RNViewManager.self).url(forResource: "hyperswitch", withExtension: "bundle")
|
|
52
|
-
#endif
|
|
53
|
-
}
|
|
106
|
+
// #endif
|
|
107
|
+
// }
|
|
54
108
|
}
|
|
55
109
|
}
|
|
110
|
+
|