react-native-hyperswitch-dev-sdk 0.4.3 → 0.4.5

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 (35) hide show
  1. package/HyperswitchSdkReactNative.podspec +4 -0
  2. package/android/build.gradle +0 -6
  3. package/android/src/main/java/com/hyperswitchsdkreactnative/modules/ReactNativeHyperswitchModule.kt +1 -5
  4. package/ios/Modules/ReactNative/HyperModule.h +25 -0
  5. package/ios/Modules/ReactNative/HyperModule.mm +169 -0
  6. package/ios/Modules/ReactNative/HyperswitchModule.swift +223 -165
  7. package/ios/Modules/ReactNative/NativeHyperswitchModule.mm +17 -12
  8. package/ios/Modules/ReactNative/NativeHyperswitchModuleImpl.swift +18 -7
  9. package/ios/Modules/ReactNative/NativePaymentElementModule.mm +122 -122
  10. package/ios/Views/Fabric/NativePaymentElementView.mm +8 -0
  11. package/ios/Views/Native/NativePaymentWidget.swift +125 -121
  12. package/ios/hyperswitchSDK/Core/HyperCVCWidget/CVCWidget.swift +9 -7
  13. package/ios/hyperswitchSDK/Core/HyperPaymentCardTextField/PaymentHandler.swift +1 -1
  14. package/ios/hyperswitchSDK/Core/HyperPaymentSheet/PaymentSheetView+UIKit.swift +79 -13
  15. package/ios/hyperswitchSDK/Core/HyperPaymentSheet/PaymentSheetView.swift +2 -2
  16. package/ios/hyperswitchSDK/Core/HyperPaymentWidget/PaymentWidget.swift +23 -19
  17. package/ios/hyperswitchSDK/Core/HyperSession/PaymentSession+UIKit.swift +14 -0
  18. package/ios/hyperswitchSDK/Core/NativeModule/HyperModuleImpl.swift +388 -0
  19. package/ios/hyperswitchSDK/Core/NativeModule/RNHeadlessManager.swift +58 -22
  20. package/ios/hyperswitchSDK/Core/NativeModule/RNViewManager.swift +84 -29
  21. package/ios/hyperswitchSDK/Core/NativeModule/hyper-Bridging-Header.h +2 -0
  22. package/ios/hyperswitchSDK/Public/RNBridgeFactory.h +28 -0
  23. package/ios/hyperswitchSDK/Public/RNBridgeFactory.mm +167 -0
  24. package/ios/hyperswitchSDK/Shared/PaymentSheet.swift +1 -1
  25. package/lib/commonjs/index.bundle.js +1 -1
  26. package/lib/commonjs/index.bundle.js.map +1 -1
  27. package/lib/module/index.bundle.js +1 -1
  28. package/lib/module/index.bundle.js.map +1 -1
  29. package/package.json +8 -4
  30. package/src/context/PaymentSession.ts +2 -0
  31. package/src/specs/NativeHyperswitchModule.ts +2 -2
  32. package/ios/Modules/ReactNative/HyperswitchSdkReactNative.h +0 -5
  33. package/ios/Modules/ReactNative/HyperswitchSdkReactNative.mm +0 -121
  34. package/ios/hyperswitchSDK/Core/NativeModule/HyperModule.m +0 -31
  35. package/ios/hyperswitchSDK/Core/NativeModule/HyperModule.swift +0 -343
@@ -14,7 +14,7 @@ public class CVCWidget: UIControl {
14
14
  private var configurationDict: [String: Any]?
15
15
  private var widgetReactTag: NSNumber?
16
16
  internal var rootReactTag: NSNumber? { widgetReactTag }
17
- private var rootView: RCTRootView?
17
+ private var rootView: UIView?
18
18
  private var cvcCallback: ((PaymentResult) -> Void)?
19
19
  private var subscribedEventNames: [String]?
20
20
  private let hyperswitch: Hyperswitch
@@ -112,12 +112,13 @@ public class CVCWidget: UIControl {
112
112
  "paymentToken": paymentToken,
113
113
  "paymentMethodId": paymentMethodId as Any,
114
114
  ]
115
- self.rootView?.bridge.enqueueJSCall(
116
- "RCTDeviceEventEmitter",
117
- method: "emit",
118
- args: ["triggerWidgetAction", payload],
119
- completion: nil
120
- )
115
+ // Bridge-based widget emit removed for payment-sheet-only build.
116
+ // RNViewManager.sharedInstance.bridge?.enqueueJSCall(
117
+ // "RCTDeviceEventEmitter",
118
+ // method: "emit",
119
+ // args: ["triggerWidgetAction", payload],
120
+ // completion: nil
121
+ // )
121
122
  }
122
123
 
123
124
  internal func dispatchPaymentEvent(type: String, payload: [String: Any]) {
@@ -134,3 +135,4 @@ public class CVCWidget: UIControl {
134
135
  self.paymentEventListener = listener
135
136
  }
136
137
  }
138
+
@@ -27,7 +27,7 @@ public class PaymentHandler: NSObject {
27
27
  self.completion = completion
28
28
 
29
29
  RNViewManager.sharedInstance.responseHandler = self
30
- HyperModule.shared?.confirm(data: paymentParams.description())
30
+ HyperModuleImpl.shared.confirm(data: paymentParams.description())
31
31
  }
32
32
  public typealias PaymentHandlerActionPaymentIntentCompletionBlock = (
33
33
  PaymentHandlerActionStatus, PaymentIntent?, NSError?
@@ -11,13 +11,13 @@ import React
11
11
  /// Extension on the PaymentSheet class to handle the presentation of the payment sheet view.
12
12
  internal extension PaymentSheet {
13
13
 
14
- /// Method to present the payment sheet view with a given root view and completion handler.
14
+ /// Present with a raw-string completion the JS bundle's exit message is forwarded
15
+ /// unchanged to the caller. Used by the React Native TurboModule path.
15
16
  private func presentWithRootView(
16
17
  from presentingViewController: UIViewController,
17
- rootView: RCTRootView,
18
- completion: @escaping (PaymentResult) -> Void
18
+ rootView: UIView,
19
+ completion: @escaping (String) -> Void
19
20
  ) {
20
-
21
21
  self.completion = completion
22
22
 
23
23
  let paymentSheetViewController = HyperUIViewController()
@@ -25,25 +25,91 @@ internal extension PaymentSheet {
25
25
  paymentSheetViewController.modalPresentationStyle = .overFullScreen
26
26
  paymentSheetViewController.view = rootView
27
27
 
28
- /// Present the payment sheet view controller modally from the presenting view controller.
28
+ // Register the sheet and VC directly with HyperModuleImpl so that
29
+ // exitPaymentsheet can call the completion without relying on
30
+ // bridge.uiManager.addUIBlock (which doesn't find Fabric-rendered views).
31
+ HyperModuleImpl.shared.registerPaymentSheet(self, vc: paymentSheetViewController)
32
+
29
33
  presentingViewController.present(paymentSheetViewController, animated: false)
30
34
  }
31
35
 
32
- /// Method to present the payment sheet view with the default configuration.
33
- func present(from presentingViewController: UIViewController, completion: @escaping (PaymentResult) -> Void) {
36
+ // MARK: - Public-facing helpers (PaymentResult overloads kept for SwiftUI / native SDK usage)
37
+
38
+ /// Present with a typed `PaymentResult` — the result is serialised to a JSON string
39
+ /// internally so the single `completion: ((String) -> Void)?` property is shared.
40
+ func present(
41
+ from presentingViewController: UIViewController,
42
+ completion: @escaping (PaymentResult) -> Void
43
+ ) {
44
+ presentWithRootView(
45
+ from: presentingViewController,
46
+ rootView: getRootView(),
47
+ completion: { raw in completion(PaymentSheet.paymentResult(from: raw)) }
48
+ )
49
+ }
50
+
51
+ /// Present with custom props and a typed `PaymentResult` completion.
52
+ func presentWithParams(
53
+ from presentingViewController: UIViewController,
54
+ props: [String: Any],
55
+ completion: @escaping (PaymentResult) -> Void
56
+ ) {
57
+ presentWithRootView(
58
+ from: presentingViewController,
59
+ rootView: getRootViewWithParams(props: props),
60
+ completion: { raw in completion(PaymentSheet.paymentResult(from: raw)) }
61
+ )
62
+ }
63
+
64
+ // MARK: - Internal raw-string overloads (used by HyperswitchModule / TurboModule path)
34
65
 
35
- // Present the payment sheet view with the root view obtained from the getRootView() method.
36
- self.presentWithRootView(from: presentingViewController, rootView: self.getRootView(), completion: completion)
66
+ /// Present with default configuration and a raw-string completion.
67
+ func present(
68
+ from presentingViewController: UIViewController,
69
+ rawCompletion: @escaping (String) -> Void
70
+ ) {
71
+ presentWithRootView(
72
+ from: presentingViewController,
73
+ rootView: getRootView(),
74
+ completion: rawCompletion
75
+ )
37
76
  }
38
77
 
39
- /// Method to present the payment sheet view with custom parameters.
78
+ /// Present with custom props and a raw-string completion.
40
79
  func presentWithParams(
41
80
  from presentingViewController: UIViewController,
42
81
  props: [String: Any],
43
- completion: @escaping ((PaymentResult) -> Void)
82
+ rawCompletion: @escaping (String) -> Void
44
83
  ) {
84
+ presentWithRootView(
85
+ from: presentingViewController,
86
+ rootView: getRootViewWithParams(props: props),
87
+ completion: rawCompletion
88
+ )
89
+ }
90
+
91
+ // MARK: - Shared result deserialiser
45
92
 
46
- // Present the payment sheet view with the root view obtained from the getRootViewWithParams() method.
47
- self.presentWithRootView(from: presentingViewController, rootView: self.getRootViewWithParams(props: props), completion: completion)
93
+ /// Convert the raw JSON string sent by the JS bundle into a typed `PaymentResult`.
94
+ /// Mirrors the logic in `HyperModuleImpl.paymentResult(from:)`.
95
+ private static func paymentResult(from raw: String) -> PaymentResult {
96
+ guard
97
+ let data = raw.data(using: .utf8),
98
+ let dict = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
99
+ let status = dict["status"] as? String
100
+ else {
101
+ return .failed(error: NSError(domain: "UNKNOWN_ERROR", code: 0,
102
+ userInfo: ["message": "An error has occurred."]))
103
+ }
104
+ switch status {
105
+ case "success", "succeeded", "completed":
106
+ return .completed(data: status)
107
+ case "cancelled", "canceled":
108
+ return .canceled(data: status)
109
+ default:
110
+ let code = (dict["code"] as? String).flatMap { $0.isEmpty ? nil : $0 } ?? "UNKNOWN_ERROR"
111
+ let message = dict["message"] as? String ?? "An error has occurred."
112
+ return .failed(error: NSError(domain: code, code: 0, userInfo: ["message": message]))
113
+ }
48
114
  }
49
115
  }
@@ -13,7 +13,7 @@ import WebKit
13
13
  internal extension PaymentSheet {
14
14
 
15
15
  /// Method to get the root view for the payment sheet based on the configured properties.
16
- func getRootView() -> RCTRootView {
16
+ func getRootView() -> UIView {
17
17
 
18
18
  let hyperswitchConfiguration = try? hyperswitchConfiguration?.toDictionary()
19
19
  let paymentSessionConfiguration = try? paymentSessionConfiguration.toDictionary()
@@ -42,7 +42,7 @@ internal extension PaymentSheet {
42
42
 
43
43
  /// Method to get the root view for the payment sheet with custom parameters.
44
44
  /// - Note: Used by Flutter and React Native Wrappers to send separate props.
45
- func getRootViewWithParams(props: [String: Any]) -> RCTRootView {
45
+ func getRootViewWithParams(props: [String: Any]) -> UIView {
46
46
 
47
47
  let hyperswitchConfiguration = try? hyperswitchConfiguration?.toDictionary()
48
48
  let paymentSessionConfiguration = try? paymentSessionConfiguration.toDictionary()
@@ -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: RCTRootView?
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,12 +124,13 @@ 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
- self.rootView?.bridge.enqueueJSCall(
128
- "RCTDeviceEventEmitter",
129
- method: "emit",
130
- args: ["updateIntentInit", payload],
131
- completion: nil
132
- )
127
+ // Bridge-based widget emit removed for payment-sheet-only build.
128
+ // RNViewManager.sharedInstance.bridge?.enqueueJSCall(
129
+ // "RCTDeviceEventEmitter",
130
+ // method: "emit",
131
+ // args: ["updateIntentInit", payload],
132
+ // completion: nil
133
+ // )
133
134
  }
134
135
  .store(in: &cancellables)
135
136
 
@@ -141,12 +142,13 @@ public class PaymentWidget: UIControl {
141
142
  "rootTag": self.widgetReactTag ?? -1,
142
143
  "sdkAuthorization": sdkAuthorization,
143
144
  ]
144
- self.rootView?.bridge.enqueueJSCall(
145
- "RCTDeviceEventEmitter",
146
- method: "emit",
147
- args: ["updateIntentComplete", payload],
148
- completion: nil
149
- )
145
+ // Bridge-based widget emit removed for payment-sheet-only build.
146
+ // RNViewManager.sharedInstance.bridge?.enqueueJSCall(
147
+ // "RCTDeviceEventEmitter",
148
+ // method: "emit",
149
+ // args: ["updateIntentComplete", payload],
150
+ // completion: nil
151
+ // )
150
152
  }
151
153
  .store(in: &cancellables)
152
154
  }
@@ -156,12 +158,13 @@ public class PaymentWidget: UIControl {
156
158
  "rootTag": self.widgetReactTag ?? -1,
157
159
  "actionType": "CONFIRM_PAYMENT_ACTION",
158
160
  ]
159
- self.rootView?.bridge.enqueueJSCall(
160
- "RCTDeviceEventEmitter",
161
- method: "emit",
162
- args: ["triggerWidgetAction", payload],
163
- completion: nil
164
- )
161
+ // Bridge-based widget emit removed for payment-sheet-only build.
162
+ // RNViewManager.sharedInstance.bridge?.enqueueJSCall(
163
+ // "RCTDeviceEventEmitter",
164
+ // method: "emit",
165
+ // args: ["triggerWidgetAction", payload],
166
+ // completion: nil
167
+ // )
165
168
  }
166
169
 
167
170
  internal func setPaymentEventListener(_ listener: PaymentEventListener?) {
@@ -210,3 +213,4 @@ public class PaymentWidget: UIControl {
210
213
  }
211
214
  }
212
215
  }
216
+
@@ -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
@@ -0,0 +1,388 @@
1
+ //
2
+ // HyperModuleImpl.swift
3
+ // Pods
4
+ //
5
+ // Created by Kuntimaddi Manideep on 22/07/26.
6
+ //
7
+
8
+ import Foundation
9
+ import React
10
+
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?
19
+
20
+ private let applePayPaymentHandler = ApplePayHandler()
21
+ private let expressCheckoutHandler = ExpressCheckoutLauncher()
22
+ private var presentCallback: RCTResponseSenderBlock? = nil
23
+
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() {
31
+ super.init()
32
+ }
33
+
34
+ // MARK: - Payment sheet registration (called from PaymentSheetView+UIKit)
35
+
36
+ internal func registerPaymentSheet(_ sheet: PaymentSheet, vc: HyperUIViewController) {
37
+ activePaymentSheet = sheet
38
+ activePaymentSheetVC = vc
39
+ }
40
+
41
+ // Bridge-backed widget helpers removed for the new-arch/bridgeless build.
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)
48
+ }
49
+ // MARK: WIP
50
+ // @objc public func confirmEC(data: [String: Any]) {
51
+ // eventEmitter?.sendEvent(withName: "confirmEC", body: data)
52
+ // }
53
+
54
+ // MARK: - Generic message passing
55
+
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)
62
+ }
63
+
64
+ @objc public func startApplePay(_ rnMessage: String, callback: @escaping RCTResponseSenderBlock) {
65
+ callback([])
66
+ }
67
+
68
+ @objc public func presentApplePay(_ rnMessage: String, callback: @escaping RCTResponseSenderBlock) {
69
+ self.presentCallback = callback
70
+ }
71
+
72
+ // MARK: - Widget (commented out — relies on RCTBridge/uiManager)
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)
77
+ }
78
+ //
79
+ @objc public func onAddPaymentMethod(_ rnMessage: String) {
80
+ // PaymentMethodManagementWidget.onAddPaymentMethod?()
81
+ }
82
+
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
101
+ }
102
+ }
103
+
104
+ @objc public func exitWidgetPaymentsheet(_ reactTag: NSNumber, result rnMessage: String, reset: Bool) {
105
+ // let result = paymentResult(from: rnMessage)
106
+ // withWidget(reactTag) { w in
107
+ // w.handleConfirmPaymentResponse(result)
108
+ // }
109
+ }
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 (commented out — rely on RCTBridge/uiManager)
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
+
185
+ private func paymentResult(from rnMessage: String) -> PaymentResult {
186
+ guard let data = rnMessage.data(using: .utf8) else {
187
+ return .failed(
188
+ error: NSError(
189
+ domain: "UNKNOWN_ERROR",
190
+ code: 0,
191
+ userInfo: ["message": "An error has occurred."]
192
+ )
193
+ )
194
+ }
195
+
196
+ do {
197
+ guard let jsonDictionary = try JSONSerialization.jsonObject(with: data, options: []) as? [String: String] else {
198
+ return .failed(
199
+ error: NSError(
200
+ domain: "UNKNOWN_ERROR",
201
+ code: 0,
202
+ userInfo: ["message": "An error has occurred."]
203
+ )
204
+ )
205
+ }
206
+
207
+ let status = jsonDictionary["status"]
208
+
209
+ if status == "success" || status == "succeeded" || status == "completed" {
210
+ return .completed(data: status ?? "success")
211
+ } else if status == "cancelled" || status == "canceled" {
212
+ return .canceled(data: status ?? "cancelled")
213
+ } else {
214
+ let error = NSError(
215
+ domain: (jsonDictionary["code"] ?? "") != "" ? jsonDictionary["code"]! : "UNKNOWN_ERROR",
216
+ code: 0,
217
+ userInfo: ["message": jsonDictionary["message"] ?? status ?? "An error has occurred."]
218
+ )
219
+ return .failed(error: error)
220
+ }
221
+ } catch {
222
+ return .failed(
223
+ error: NSError(
224
+ domain: "UNKNOWN_ERROR",
225
+ code: 0,
226
+ userInfo: ["message": "An error has occurred."]
227
+ )
228
+ )
229
+ }
230
+ }
231
+
232
+ private func exitCardFormInternal(_ rnMessage: String) {
233
+ var response: String?
234
+ var error: NSError?
235
+
236
+ if let data = rnMessage.data(using: .utf8) {
237
+ do {
238
+ if let jsonDictionary = try JSONSerialization.jsonObject(with: data, options: []) as? [String: String] {
239
+ let status = jsonDictionary["status"]
240
+
241
+ if status == "failed" || status == "requires_payment_method" {
242
+ error = NSError(
243
+ domain: (jsonDictionary["code"] ?? "") != "" ? jsonDictionary["code"]! : "UNKNOWN_ERROR",
244
+ code: 0,
245
+ userInfo: ["message": jsonDictionary["message"] ?? "An error has occurred."]
246
+ )
247
+ } else {
248
+ response = status
249
+ }
250
+ RNViewManager.sharedInstance.responseHandler?.didReceiveResponse(response: response, error: error)
251
+ } else {
252
+ RNViewManager.sharedInstance.responseHandler?.didReceiveResponse(
253
+ response: "failed",
254
+ error: NSError(domain: "UNKNOWN_ERROR", code: 0, userInfo: ["message": "An error has occurred."])
255
+ )
256
+ }
257
+ } catch {
258
+ RNViewManager.sharedInstance.responseHandler?.didReceiveResponse(
259
+ response: "failed",
260
+ error: NSError(domain: "UNKNOWN_ERROR", code: 0, userInfo: ["message": "An error has occurred."])
261
+ )
262
+ }
263
+ } else {
264
+ RNViewManager.sharedInstance.responseHandler?.didReceiveResponse(
265
+ response: "failed",
266
+ error: NSError(domain: "UNKNOWN_ERROR", code: 0, userInfo: ["message": "An error has occurred."])
267
+ )
268
+ }
269
+ }
270
+
271
+ private func exitSheet(_ rnMessage: String) {
272
+ var response: String?
273
+ var error: NSError?
274
+
275
+ if let data = rnMessage.data(using: .utf8) {
276
+ do {
277
+ if let jsonDictionary = try JSONSerialization.jsonObject(with: data, options: []) as? [String: String] {
278
+ let status = jsonDictionary["status"]
279
+
280
+ if status == "failed" || status == "requires_payment_method" {
281
+ error = NSError(
282
+ domain: (jsonDictionary["code"] ?? "") != "" ? jsonDictionary["code"]! : "UNKNOWN_ERROR",
283
+ code: 0,
284
+ userInfo: ["message": jsonDictionary["message"] ?? "An error has occurred."]
285
+ )
286
+ } else {
287
+ response = status
288
+ }
289
+ RNViewManager.sharedInstance.responseHandler?.didReceiveResponse(response: response, error: error)
290
+ } else {
291
+ RNViewManager.sharedInstance.responseHandler?.didReceiveResponse(
292
+ response: "failed",
293
+ error: NSError(domain: "UNKNOWN_ERROR", code: 0, userInfo: ["message": "An error has occurred."])
294
+ )
295
+ }
296
+ } catch {
297
+ RNViewManager.sharedInstance.responseHandler?.didReceiveResponse(
298
+ response: "failed",
299
+ error: NSError(domain: "UNKNOWN_ERROR", code: 0, userInfo: ["message": "An error has occurred."])
300
+ )
301
+ }
302
+ } else {
303
+ RNViewManager.sharedInstance.responseHandler?.didReceiveResponse(
304
+ response: "failed",
305
+ error: NSError(domain: "UNKNOWN_ERROR", code: 0, userInfo: ["message": "An error has occurred."])
306
+ )
307
+ }
308
+ DispatchQueue.main.async {
309
+ if let view = RNViewManager.sharedInstance.rootView {
310
+ let reactNativeVC: UIViewController? = view.reactViewController()
311
+ reactNativeVC?.dismiss(animated: false, completion: nil)
312
+ }
313
+ }
314
+ }
315
+
316
+ // MARK: - Bridge-backed view lookup helpers (commented out for new-arch/bridgeless build)
317
+
318
+ // private func withWidget(_ rootTag: NSNumber, _ block: @escaping (PaymentWidget) -> Void) {
319
+ // guard let bridge = self.bridge else { return }
320
+ // RCTGetUIManagerQueue().async {
321
+ // bridge.uiManager.addUIBlock { _, viewRegistry in
322
+ // guard let view = viewRegistry?[rootTag] else { return }
323
+ // var current: UIView? = view
324
+ // while let v = current {
325
+ // if let widget = v as? PaymentWidget {
326
+ // block(widget)
327
+ // return
328
+ // }
329
+ // current = v.superview
330
+ // }
331
+ // }
332
+ // }
333
+ // }
334
+ //
335
+ // private func withNativePaymentWidgetView(_ rootTag: NSNumber, _ block: @escaping (PaymentWidget) -> Void) {
336
+ // guard let bridge = self.bridge else { return }
337
+ // RCTGetUIManagerQueue().async {
338
+ // bridge.uiManager.addUIBlock { _, viewRegistry in
339
+ // guard let view = viewRegistry?[rootTag] else { return }
340
+ // var current: UIView? = view
341
+ // while let v = current {
342
+ // if let nativeWidget = v as? PaymentWidget {
343
+ // block(nativeWidget)
344
+ // return
345
+ // }
346
+ // current = v.superview
347
+ // }
348
+ // }
349
+ // }
350
+ // }
351
+ //
352
+ // private func resolveSubscribingTarget(_ rootTag: NSNumber, _ block: @escaping (AnyObject?) -> Void) {
353
+ // guard let bridge = self.bridge else {
354
+ // DispatchQueue.main.async { block(nil) }
355
+ // return
356
+ // }
357
+ // RCTGetUIManagerQueue().async {
358
+ // bridge.uiManager.addUIBlock { _, viewRegistry in
359
+ // guard let view = viewRegistry?[rootTag] else {
360
+ // DispatchQueue.main.async { block(nil) }
361
+ // return
362
+ // }
363
+ // var current: UIView? = view
364
+ // while let v = current {
365
+ // if v is PaymentWidget || v is CVCWidget {
366
+ // DispatchQueue.main.async { block(v) }
367
+ // return
368
+ // }
369
+ // current = v.superview
370
+ // }
371
+ // let sheet = (view.reactViewController() as? HyperUIViewController)?.paymentSheet
372
+ // DispatchQueue.main.async { block(sheet) }
373
+ // }
374
+ // }
375
+ // }
376
+ //
377
+ // private func withPaymentSheet(_ rootTag: NSNumber, _ block: @escaping (UIViewController?, PaymentSheet?) -> Void) {
378
+ // guard let bridge = self.bridge else { return }
379
+ // RCTGetUIManagerQueue().async {
380
+ // bridge.uiManager.addUIBlock { _, viewRegistry in
381
+ // let view = viewRegistry?[rootTag]
382
+ // let vc = view?.reactViewController() as? HyperUIViewController
383
+ // let sheet = vc?.paymentSheet
384
+ // DispatchQueue.main.async { block(vc, sheet) }
385
+ // }
386
+ // }
387
+ // }
388
+ }