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.
Files changed (34) hide show
  1. package/HyperswitchSdkReactNative.podspec +4 -0
  2. package/android/build.gradle +0 -6
  3. package/android/hyperswitch_autolinking.gradle +1 -1
  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 +26 -22
  12. package/ios/hyperswitchSDK/Core/HyperCVCWidget/CVCWidget.swift +3 -2
  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 +5 -4
  17. package/ios/hyperswitchSDK/Core/HyperSession/PaymentSession+UIKit.swift +14 -0
  18. package/ios/hyperswitchSDK/Core/NativeModule/{HyperModule.swift → HyperModuleImpl.swift} +156 -113
  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 +6 -2
  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
@@ -119,9 +119,18 @@ public class NativeHyperswitchModuleImpl: NSObject {
119
119
  ])
120
120
  return
121
121
  }
122
+
123
+ // The SDK's getRootViewWithParams wraps the whole `props` dictionary
124
+ // under the `configuration` key, so we must pass the contents that
125
+ // belong inside `configuration`: the merchant-supplied configuration
126
+ // plus the session/hyperswitch metadata.
127
+ var sheetProps = params["configuration"] as? [String: Any] ?? [:]
128
+ sheetProps["hyperswitchConfig"] = params["hyperswitchConfig"]
129
+ sheetProps["paymentSessionConfig"] = params["paymentSessionConfig"]
130
+
122
131
  session.presentPaymentSheetWithParams(
123
132
  viewController: vc,
124
- params: params,
133
+ params: sheetProps,
125
134
  completion: { result in
126
135
  switch result {
127
136
  case .completed(let data):
@@ -264,11 +273,12 @@ public class NativeHyperswitchModuleImpl: NSObject {
264
273
  }
265
274
  }
266
275
 
267
- // MARK: - confirmWithCustomerLastUsedPaymentMethod (no params — matches Android)
268
- // Android: TODO("Not yet implemented")
276
+ // MARK: - confirmWithCustomerLastUsedPaymentMethod
277
+ // JS / Android: confirmWithCustomerLastUsedPaymentMethod(reactTag: Double, promise: Promise?)
269
278
 
270
- @objc(confirmWithCustomerLastUsedPaymentMethodWithResolve:reject:)
279
+ @objc(confirmWithCustomerLastUsedPaymentMethod:resolve:reject:)
271
280
  public func confirmWithCustomerLastUsedPaymentMethod(
281
+ reactTag: Double,
272
282
  resolve: @escaping RCTPromiseResolveBlock,
273
283
  reject: @escaping RCTPromiseRejectBlock
274
284
  ) {
@@ -276,11 +286,12 @@ public class NativeHyperswitchModuleImpl: NSObject {
276
286
  resolve("{\"status\":\"failed\",\"message\":\"Not yet implemented\"}")
277
287
  }
278
288
 
279
- // MARK: - confirmWithCustomerDefaultPaymentMethod (no params — matches Android)
280
- // Android: TODO("Not yet implemented")
289
+ // MARK: - confirmWithCustomerDefaultPaymentMethod
290
+ // JS / Android: confirmWithCustomerDefaultPaymentMethod(reactTag: Double, promise: Promise?)
281
291
 
282
- @objc(confirmWithCustomerDefaultPaymentMethodWithResolve:reject:)
292
+ @objc(confirmWithCustomerDefaultPaymentMethod:resolve:reject:)
283
293
  public func confirmWithCustomerDefaultPaymentMethod(
294
+ reactTag: Double,
284
295
  resolve: @escaping RCTPromiseResolveBlock,
285
296
  reject: @escaping RCTPromiseRejectBlock
286
297
  ) {
@@ -1,129 +1,129 @@
1
+ ////
2
+ //// NativePaymentElementModule.mm
3
+ ////
4
+ //// TurboModule (New Architecture) for NativePaymentElementModule.
5
+ //// Module name: "NativePaymentElementModule"
6
+ ////
7
+ //// Matches Android's NativePaymentWidgetModule (NativePaymentElementModuleSpec):
8
+ //// confirmPayment(reactTag, callback)
9
+ //// updateIntentInitForWidget(reactTag, callback)
10
+ //// updateIntentCompleteForWidget(reactTag, sdkAuthorization, callback)
11
+ ////
12
+ //// Old arch (bridge): the view-manager commands (same methods) live on NativePaymentWidget
13
+ //// (RCTViewManager) which is accessible via NativeModules.NativePaymentWidget.
14
+ //// NativeHyperswitchSdk.ts still uses that path, so both old and new arch work.
15
+ ////
16
+ //// New arch (TurboModule): this module is registered as "NativePaymentElementModule" and is
17
+ //// the spec-compliant TurboModule counterpart. It delegates to the
18
+ //// inner NativePaymentWidgetView found via NativePaymentWidgetViewRegistry.
19
+ ////
1
20
  //
2
- // NativePaymentElementModule.mm
21
+ //#import <React/RCTBridgeModule.h>
22
+ //#import <React/RCTUIManager.h>
3
23
  //
4
- // TurboModule (New Architecture) for NativePaymentElementModule.
5
- // Module name: "NativePaymentElementModule"
24
+ //#if __has_include("HyperswitchSdkReactNative-Swift.h")
25
+ //#import "HyperswitchSdkReactNative-Swift.h"
26
+ //#else
27
+ //#import <HyperswitchSdkReactNative/HyperswitchSdkReactNative-Swift.h>
28
+ //#endif
6
29
  //
7
- // Matches Android's NativePaymentWidgetModule (NativePaymentElementModuleSpec):
8
- // confirmPayment(reactTag, callback)
9
- // updateIntentInitForWidget(reactTag, callback)
10
- // updateIntentCompleteForWidget(reactTag, sdkAuthorization, callback)
30
+ //#import "NativePaymentWidgetViewRegistry.h"
11
31
  //
12
- // Old arch (bridge): the view-manager commands (same methods) live on NativePaymentWidget
13
- // (RCTViewManager) which is accessible via NativeModules.NativePaymentWidget.
14
- // NativeHyperswitchSdk.ts still uses that path, so both old and new arch work.
32
+ //#ifdef RCT_NEW_ARCH_ENABLED
33
+ //#import <HyperswitchSdkReactNativeSpec/HyperswitchSdkReactNativeSpec.h>
34
+ //#endif
15
35
  //
16
- // New arch (TurboModule): this module is registered as "NativePaymentElementModule" and is
17
- // the spec-compliant TurboModule counterpart. It delegates to the
18
- // inner NativePaymentWidgetView found via NativePaymentWidgetViewRegistry.
36
+ //// ─────────────────────────────────────────────────────────────────────────────
37
+ //// Private helper: find NativePaymentWidgetView by React tag.
38
+ ////
39
+ //// Checks NativePaymentWidgetViewRegistry first (covers both old-arch direct
40
+ //// NativePaymentWidgetView instances registered there AND the inner widget views
41
+ //// registered by NativePaymentElementView for Fabric).
42
+ //// ─────────────────────────────────────────────────────────────────────────────
43
+ //static NativePaymentWidget * _Nullable widgetViewForTag(NSNumber *tag)
44
+ //{
45
+ // UIView *v = [[NativePaymentWidgetViewRegistry shared] viewForTag:tag];
46
+ // return [v isKindOfClass:[NativePaymentWidget class]]
47
+ // ? (NativePaymentWidget *)v
48
+ // : nil;
49
+ //}
19
50
  //
20
-
21
- #import <React/RCTBridgeModule.h>
22
- #import <React/RCTUIManager.h>
23
-
24
- #if __has_include("HyperswitchSdkReactNative-Swift.h")
25
- #import "HyperswitchSdkReactNative-Swift.h"
26
- #else
27
- #import <HyperswitchSdkReactNative/HyperswitchSdkReactNative-Swift.h>
28
- #endif
29
-
30
- #import "NativePaymentWidgetViewRegistry.h"
31
-
32
- #ifdef RCT_NEW_ARCH_ENABLED
33
- #import <HyperswitchSdkReactNativeSpec/HyperswitchSdkReactNativeSpec.h>
34
- #endif
35
-
36
- // ─────────────────────────────────────────────────────────────────────────────
37
- // Private helper: find NativePaymentWidgetView by React tag.
51
+ //// ─────────────────────────────────────────────────────────────────────────────
38
52
  //
39
- // Checks NativePaymentWidgetViewRegistry first (covers both old-arch direct
40
- // NativePaymentWidgetView instances registered there AND the inner widget views
41
- // registered by NativePaymentElementView for Fabric).
42
- // ─────────────────────────────────────────────────────────────────────────────
43
- static NativePaymentWidgetView * _Nullable widgetViewForTag(NSNumber *tag)
44
- {
45
- UIView *v = [[NativePaymentWidgetViewRegistry shared] viewForTag:tag];
46
- return [v isKindOfClass:[NativePaymentWidgetView class]]
47
- ? (NativePaymentWidgetView *)v
48
- : nil;
49
- }
50
-
51
- // ─────────────────────────────────────────────────────────────────────────────
52
-
53
- // Use RCTBridgeModule for both arches; getTurboModule: below handles new arch wiring.
54
- @interface NativePaymentElementModule : NSObject <RCTBridgeModule>
55
- @end
56
-
57
- @implementation NativePaymentElementModule
58
-
59
- RCT_EXPORT_MODULE(NativePaymentElementModule)
60
-
61
- // ── confirmPayment ────────────────────────────────────────────────────────────
62
- // JS / Android: confirmPayment(reactTag: number, callback)
63
- // Mirrors Android NativePaymentWidgetModule.confirmPayment (mostly a stub there).
64
- // On iOS we do the real work via the shared registry.
65
- RCT_EXPORT_METHOD(confirmPayment:(double)reactTag
66
- callback:(RCTResponseSenderBlock)callback)
67
- {
68
- NSNumber *tag = @((NSInteger)reactTag);
69
- dispatch_async(dispatch_get_main_queue(), ^{
70
- NativePaymentWidgetView *view = widgetViewForTag(tag);
71
- if (view) {
72
- [view confirmPayment:callback];
73
- } else {
74
- callback(@[@{@"status": @"failed",
75
- @"code": @"WIDGET_NOT_FOUND",
76
- @"message": [NSString stringWithFormat:@"Widget not found for tag %@", tag]}]);
77
- }
78
- });
79
- }
80
-
81
- // ── updateIntentInitForWidget ─────────────────────────────────────────────────
82
- RCT_EXPORT_METHOD(updateIntentInitForWidget:(double)reactTag
83
- callback:(RCTResponseSenderBlock)callback)
84
- {
85
- NSNumber *tag = @((NSInteger)reactTag);
86
- dispatch_async(dispatch_get_main_queue(), ^{
87
- NativePaymentWidgetView *view = widgetViewForTag(tag);
88
- if (view) {
89
- [view updateIntentInit:callback];
90
- } else {
91
- callback(@[@{@"status": @"failed",
92
- @"code": @"WIDGET_NOT_FOUND",
93
- @"message": [NSString stringWithFormat:@"Widget not found for tag %@", tag]}]);
94
- }
95
- });
96
- }
97
-
98
- // ── updateIntentCompleteForWidget ─────────────────────────────────────────────
99
- RCT_EXPORT_METHOD(updateIntentCompleteForWidget:(double)reactTag
100
- sdkAuthorization:(NSString *)sdkAuthorization
101
- callback:(RCTResponseSenderBlock)callback)
102
- {
103
- NSNumber *tag = @((NSInteger)reactTag);
104
- dispatch_async(dispatch_get_main_queue(), ^{
105
- NativePaymentWidgetView *view = widgetViewForTag(tag);
106
- if (view) {
107
- [view updateIntentCompleteWithSdkAuthorization:sdkAuthorization resolve:callback];
108
- } else {
109
- callback(@[@{@"status": @"failed",
110
- @"code": @"WIDGET_NOT_FOUND",
111
- @"message": [NSString stringWithFormat:@"Widget not found for tag %@", tag]}]);
112
- }
113
- });
114
- }
115
-
116
- // ── TurboModule wiring (New Architecture) ─────────────────────────────────────
117
- // NativePaymentElementModuleSpecJSI is generated by iOS codegen from
118
- // NativePaymentElementModule.ts → HyperswitchSdkReactNativeSpec pod.
119
- #ifdef RCT_NEW_ARCH_ENABLED
120
- - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
121
- (const facebook::react::ObjCTurboModule::InitParams &)params
122
- {
123
- return std::make_shared<facebook::react::NativePaymentElementModuleSpecJSI>(params);
124
- }
125
- #endif
126
-
127
- + (BOOL)requiresMainQueueSetup { return NO; }
128
-
129
- @end
53
+ //// Use RCTBridgeModule for both arches; getTurboModule: below handles new arch wiring.
54
+ //@interface NativePaymentElementModule : NSObject <RCTBridgeModule>
55
+ //@end
56
+ //
57
+ //@implementation NativePaymentElementModule
58
+ //
59
+ //RCT_EXPORT_MODULE(NativePaymentElementModule)
60
+ //
61
+ //// ── confirmPayment ────────────────────────────────────────────────────────────
62
+ //// JS / Android: confirmPayment(reactTag: number, callback)
63
+ //// Mirrors Android NativePaymentWidgetModule.confirmPayment (mostly a stub there).
64
+ //// On iOS we do the real work via the shared registry.
65
+ //RCT_EXPORT_METHOD(confirmPayment:(double)reactTag
66
+ // callback:(RCTResponseSenderBlock)callback)
67
+ //{
68
+ // NSNumber *tag = @((NSInteger)reactTag);
69
+ //// dispatch_async(dispatch_get_main_queue(), ^{
70
+ //// NativePaymentWidget *view = widgetViewForTag(tag);
71
+ //// if (view) {
72
+ //// [view confirmPayment:callback];
73
+ //// } else {
74
+ //// callback(@[@{@"status": @"failed",
75
+ //// @"code": @"WIDGET_NOT_FOUND",
76
+ //// @"message": [NSString stringWithFormat:@"Widget not found for tag %@", tag]}]);
77
+ //// }
78
+ //// });
79
+ //}
80
+ //
81
+ //// ── updateIntentInitForWidget ─────────────────────────────────────────────────
82
+ //RCT_EXPORT_METHOD(updateIntentInitForWidget:(double)reactTag
83
+ // callback:(RCTResponseSenderBlock)callback)
84
+ //{
85
+ // NSNumber *tag = @((NSInteger)reactTag);
86
+ //// dispatch_async(dispatch_get_main_queue(), ^{
87
+ //// NativePaymentWidgetView *view = widgetViewForTag(tag);
88
+ //// if (view) {
89
+ //// [view updateIntentInit:callback];
90
+ //// } else {
91
+ //// callback(@[@{@"status": @"failed",
92
+ //// @"code": @"WIDGET_NOT_FOUND",
93
+ //// @"message": [NSString stringWithFormat:@"Widget not found for tag %@", tag]}]);
94
+ //// }
95
+ //// });
96
+ //}
97
+ //
98
+ //// ── updateIntentCompleteForWidget ─────────────────────────────────────────────
99
+ //RCT_EXPORT_METHOD(updateIntentCompleteForWidget:(double)reactTag
100
+ // sdkAuthorization:(NSString *)sdkAuthorization
101
+ // callback:(RCTResponseSenderBlock)callback)
102
+ //{
103
+ //// NSNumber *tag = @((NSInteger)reactTag);
104
+ //// dispatch_async(dispatch_get_main_queue(), ^{
105
+ //// NativePaymentWidgetView *view = widgetViewForTag(tag);
106
+ //// if (view) {
107
+ //// [view updateIntentCompleteWithSdkAuthorization:sdkAuthorization resolve:callback];
108
+ //// } else {
109
+ //// callback(@[@{@"status": @"failed",
110
+ //// @"code": @"WIDGET_NOT_FOUND",
111
+ //// @"message": [NSString stringWithFormat:@"Widget not found for tag %@", tag]}]);
112
+ //// }
113
+ //// });
114
+ //}
115
+ //
116
+ //// ── TurboModule wiring (New Architecture) ─────────────────────────────────────
117
+ //// NativePaymentElementModuleSpecJSI is generated by iOS codegen from
118
+ //// NativePaymentElementModule.ts → HyperswitchSdkReactNativeSpec pod.
119
+ //#ifdef RCT_NEW_ARCH_ENABLED
120
+ //- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
121
+ // (const facebook::react::ObjCTurboModule::InitParams &)params
122
+ //{
123
+ // return std::make_shared<facebook::react::NativePaymentElementModuleSpecJSI>(params);
124
+ //}
125
+ //#endif
126
+ //
127
+ //+ (BOOL)requiresMainQueueSetup { return NO; }
128
+ //
129
+ //@end
@@ -22,6 +22,14 @@
22
22
  #import "NativePaymentElementView.h"
23
23
  #import "NativePaymentWidgetViewRegistry.h"
24
24
 
25
+ // Defines RCTPromiseResolveBlock/RCTPromiseRejectBlock and RCTDirectEventBlock,
26
+ // referenced by the HyperswitchModule / NativePaymentWidgetView interfaces
27
+ // declared in the generated Swift header below.
28
+ #import <React/RCTBridgeModule.h>
29
+ #import <React/RCTComponent.h>
30
+
31
+ #include <folly/json.h>
32
+
25
33
  #import <react/renderer/components/HyperswitchSdkReactNativeSpec/ComponentDescriptors.h>
26
34
  #import <react/renderer/components/HyperswitchSdkReactNativeSpec/EventEmitters.h>
27
35
  #import <react/renderer/components/HyperswitchSdkReactNativeSpec/Props.h>
@@ -42,7 +42,7 @@ internal class NativePaymentWidget: RCTViewManager {
42
42
  }
43
43
  // New-arch (Fabric) path: the Fabric NativePaymentElementView registered
44
44
  // the inner NativePaymentWidgetView in the shared registry by the same tag.
45
- // if let view = NativePaymentWidgetViewRegistry.shared.view(forTag: reactTag) as? NativePaymentWidgetView {
45
+ // if let view = NativePaymentWidgetViewRegistry.shared().view(forTag: reactTag) as? NativePaymentWidgetView {
46
46
  // view.confirmPayment(rnCallback)
47
47
  // return
48
48
  // }
@@ -56,7 +56,7 @@ internal class NativePaymentWidget: RCTViewManager {
56
56
  view.updateIntentInit(rnCallback)
57
57
  return
58
58
  }
59
- // if let view = NativePaymentWidgetViewRegistry.shared.view(forTag: rootTag) as? NativePaymentWidgetView {
59
+ // if let view = NativePaymentWidgetViewRegistry.shared().view(forTag: rootTag) as? NativePaymentWidgetView {
60
60
  // view.updateIntentInit(rnCallback)
61
61
  // return
62
62
  // }
@@ -74,7 +74,7 @@ internal class NativePaymentWidget: RCTViewManager {
74
74
  view.updateIntentComplete(sdkAuthorization: sdkAuthorization, resolve: rnCallback)
75
75
  return
76
76
  }
77
- // if let view = NativePaymentWidgetViewRegistry.shared.view(forTag: rootTag) as? NativePaymentWidgetView {
77
+ // if let view = NativePaymentWidgetViewRegistry.shared().view(forTag: rootTag) as? NativePaymentWidgetView {
78
78
  // view.updateIntentComplete(sdkAuthorization: sdkAuthorization, resolve: rnCallback)
79
79
  // return
80
80
  // }
@@ -93,7 +93,7 @@ internal class NativePaymentWidget: RCTViewManager {
93
93
  view.confirmCVCPayment(paymentToken: paymentToken, paymentMethodId: paymentMethodId, resolve: rnCallback)
94
94
  return
95
95
  }
96
- // if let view = NativePaymentWidgetViewRegistry.shared.view(forTag: reactTag) as? NativePaymentWidgetView {
96
+ // if let view = NativePaymentWidgetViewRegistry.shared().view(forTag: reactTag) as? NativePaymentWidgetView {
97
97
  // view.confirmCVCPayment(paymentToken: paymentToken, paymentMethodId: paymentMethodId, resolve: rnCallback)
98
98
  // return
99
99
  // }
@@ -102,20 +102,24 @@ internal class NativePaymentWidget: RCTViewManager {
102
102
  }
103
103
  }
104
104
 
105
- // @objc exposes this class to Objective-C++ so the Fabric component
106
- // (NativePaymentElementView.mm) can instantiate it and type-check it.
105
+ // public + @objc exposes this class (and the members below marked @objc) in the
106
+ // generated HyperswitchSdkReactNative-Swift.h header so the Objective-C++ Fabric
107
+ // component (NativePaymentElementView.mm) and TurboModule (NativePaymentElementModule.mm)
108
+ // can instantiate it, type-check it, and call its commands. Framework targets only emit
109
+ // public declarations into that header, even for callers in the same module.
107
110
  @objc(NativePaymentWidgetView)
108
- internal class NativePaymentWidgetView: UIView {
111
+ public class NativePaymentWidgetView: UIView {
109
112
 
110
113
  private var paymentWidget: PaymentWidget?
111
114
  private var cvcWidget: CVCWidget?
112
115
  internal var cvcWidgetRef: CVCWidget? { cvcWidget }
113
- // Internal (not private) so the Fabric wrapper can set these via the ObjC bridge.
114
- @objc var widgetType: String?
115
- @objc var sdkAuthorization: String?
116
- @objc var options: [String: Any]?
117
- @objc var onPaymentEvent: RCTDirectEventBlock?
118
- @objc var onPaymentResult: RCTDirectEventBlock?
116
+ // Public (not just internal) so these show up in the generated Objective-C
117
+ // header for the Fabric wrapper (NativePaymentElementView.mm) to set via the ObjC bridge.
118
+ @objc public var widgetType: String?
119
+ @objc public var sdkAuthorization: String?
120
+ @objc public var options: [String: Any]?
121
+ @objc public var onPaymentEvent: RCTDirectEventBlock?
122
+ @objc public var onPaymentResult: RCTDirectEventBlock?
119
123
  private var responseSenderCallback: RCTResponseSenderBlock?
120
124
  private var appliedConfigKey: String?
121
125
 
@@ -252,7 +256,7 @@ internal class NativePaymentWidgetView: UIView {
252
256
  }
253
257
  }
254
258
 
255
- @objc func didSetProps() {
259
+ @objc public func didSetProps() {
256
260
  guard isSupportedWidgetType(), let sdkAuthorization = effectiveSdkAuthorization() else { return }
257
261
 
258
262
  let configKey = [widgetType ?? "", effectivePublishableKey() ?? "", effectiveProfileId() ?? "", sdkAuthorization].joined(separator: ":")
@@ -319,11 +323,11 @@ internal class NativePaymentWidgetView: UIView {
319
323
  }
320
324
  }
321
325
 
322
- override func didSetProps(_ changedProps: [String]) {
326
+ public override func didSetProps(_ changedProps: [String]) {
323
327
  self.didSetProps()
324
328
  }
325
329
 
326
- override init(frame: CGRect) {
330
+ public override init(frame: CGRect) {
327
331
  super.init(frame: frame)
328
332
  }
329
333
 
@@ -331,13 +335,13 @@ internal class NativePaymentWidgetView: UIView {
331
335
  fatalError("init(coder:) has not been implemented")
332
336
  }
333
337
 
334
- internal override func layoutSubviews() {
338
+ public override func layoutSubviews() {
335
339
  super.layoutSubviews()
336
340
  paymentWidget?.frame = bounds
337
341
  cvcWidget?.frame = bounds
338
342
  }
339
343
 
340
- internal func confirmPayment(_ rnCallback: @escaping RCTResponseSenderBlock) {
344
+ @objc public func confirmPayment(_ rnCallback: @escaping RCTResponseSenderBlock) {
341
345
  // avoiding duplicate confirm calls (confirmPayment triggered multiple times from RN layer)
342
346
  if self.responseSenderCallback != nil {
343
347
  let response = ["status": "failed", "error": "invalid call"]
@@ -358,7 +362,7 @@ internal class NativePaymentWidgetView: UIView {
358
362
  paymentWidget.confirm()
359
363
  }
360
364
 
361
- internal func updateIntentInit(_ resolve: @escaping RCTResponseSenderBlock) {
365
+ @objc public func updateIntentInit(_ resolve: @escaping RCTResponseSenderBlock) {
362
366
  guard let tag = rctRootTag else {
363
367
  resolve([["status": "failed", "message": "Widget root tag not found"]])
364
368
  return
@@ -370,7 +374,7 @@ internal class NativePaymentWidgetView: UIView {
370
374
  }
371
375
 
372
376
  let eventData: [String: Any] = ["rootTag": tag]
373
- RNViewManager.sharedInstance.bridge.enqueueJSCall(
377
+ RNViewManager.sharedInstance.bridge?.enqueueJSCall(
374
378
  "RCTDeviceEventEmitter",
375
379
  method: "emit",
376
380
  args: ["updateIntentInit", eventData],
@@ -378,7 +382,7 @@ internal class NativePaymentWidgetView: UIView {
378
382
  )
379
383
  }
380
384
 
381
- internal func updateIntentComplete(sdkAuthorization: String, resolve: @escaping RCTResponseSenderBlock) {
385
+ @objc public func updateIntentComplete(sdkAuthorization: String, resolve: @escaping RCTResponseSenderBlock) {
382
386
  guard let tag = rctRootTag else {
383
387
  resolve([["status": "failed", "message": "Widget root tag not found"]])
384
388
  return
@@ -393,7 +397,7 @@ internal class NativePaymentWidgetView: UIView {
393
397
  "rootTag": tag,
394
398
  "sdkAuthorization": sdkAuthorization,
395
399
  ]
396
- RNViewManager.sharedInstance.bridge.enqueueJSCall(
400
+ RNViewManager.sharedInstance.bridge?.enqueueJSCall(
397
401
  "RCTDeviceEventEmitter",
398
402
  method: "emit",
399
403
  args: ["updateIntentComplete", eventData],
@@ -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,7 +112,7 @@ public class CVCWidget: UIControl {
112
112
  "paymentToken": paymentToken,
113
113
  "paymentMethodId": paymentMethodId as Any,
114
114
  ]
115
- self.rootView?.bridge.enqueueJSCall(
115
+ RNViewManager.sharedInstance.bridge?.enqueueJSCall(
116
116
  "RCTDeviceEventEmitter",
117
117
  method: "emit",
118
118
  args: ["triggerWidgetAction", payload],
@@ -134,3 +134,4 @@ public class CVCWidget: UIControl {
134
134
  self.paymentEventListener = listener
135
135
  }
136
136
  }
137
+
@@ -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()