@juspay-tech/react-native-hyperswitch 1.3.4 → 1.3.6

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 (46) hide show
  1. package/android/build.gradle +22 -1
  2. package/android/src/main/assets/hyperswitch.bundle +590 -590
  3. package/android/src/main/java/com/hyperswitchsdkreactnative/modules/NativeLog.kt +107 -0
  4. package/android/src/main/java/com/hyperswitchsdkreactnative/modules/ReactNativeHyperswitchModule.kt +318 -117
  5. package/android/src/main/java/io/hyperswitch/paymentsession/ExitHeadlessCallBackManager.kt +4 -2
  6. package/android/src/main/java/io/hyperswitch/paymentsession/PaymentSessionHandlerImpl.kt +20 -4
  7. package/android/src/main/java/io/hyperswitch/paymentsession/PaymentSessionReactLauncher.kt +104 -9
  8. package/android/src/main/java/io/hyperswitch/paymentsheet/PaymentResult.kt +7 -1
  9. package/android/src/main/java/io/hyperswitch/react/HyperModule.kt +13 -0
  10. package/android/src/main/java/io/hyperswitch/view/PaymentWidgetView.kt +27 -4
  11. package/android/src/rn79/assets/hyperswitch.bundle +590 -590
  12. package/android/src/rn79/java/io/hyperswitch/react/HyperFragment.kt +20 -0
  13. package/android/src/rn81/java/io/hyperswitch/react/HyperFragment.kt +20 -0
  14. package/ios/Modules/ReactNative/NativeHyperswitchModuleImpl.swift +3 -1
  15. package/ios/hyperswitchSDK/Core/ApplePay/ApplePayHandler.swift +49 -38
  16. package/ios/hyperswitchSDK/Core/HyperPaymentSheet/PaymentSheetView+UIKit.swift +1 -5
  17. package/ios/hyperswitchSDK/Core/NativeModule/HyperModuleImpl.swift +49 -34
  18. package/ios/hyperswitchSDK/Core/NativeModule/RNHeadlessManager.swift +2 -12
  19. package/ios/hyperswitchSDK/Core/Resources/hyperswitch-rn82plus.bundle +590 -590
  20. package/ios/hyperswitchSDK/Core/Resources/hyperswitch.bundle +590 -590
  21. package/ios/hyperswitchSDK/Shared/Environment.swift +49 -3
  22. package/ios/hyperswitchSDK/Shared/HyperswitchEnvironment.swift +1 -0
  23. package/ios/hyperswitchSDK/Shared/NativeLogger/LogBuilder.swift +15 -0
  24. package/ios/hyperswitchSDK/Shared/NativeLogger/LogManager.swift +10 -2
  25. package/ios/hyperswitchSDK/Shared/NativeLogger/LogPayload.swift +1 -0
  26. package/ios/hyperswitchSDK/Shared/NativeLogger/NativeLog.swift +101 -0
  27. package/ios/hyperswitchSDK/Shared/PaymentSession.swift +8 -1
  28. package/lib/commonjs/index.bundle.js +2 -2
  29. package/lib/commonjs/index.bundle.js.map +1 -1
  30. package/lib/module/index.bundle.js +2 -2
  31. package/lib/module/index.bundle.js.map +1 -1
  32. package/lib/typescript/src/context/HyperElements.d.ts.map +1 -1
  33. package/lib/typescript/src/index.d.ts.map +1 -1
  34. package/lib/typescript/src/native/NativeResponseMapper.d.ts +2 -1
  35. package/lib/typescript/src/native/NativeResponseMapper.d.ts.map +1 -1
  36. package/lib/typescript/src/session/SavedPaymentMethods.d.ts.map +1 -1
  37. package/lib/typescript/src/session/presentPaymentSheet.d.ts +0 -12
  38. package/lib/typescript/src/session/presentPaymentSheet.d.ts.map +1 -1
  39. package/lib/typescript/src/views/PaymentElement.d.ts.map +1 -1
  40. package/package.json +1 -1
  41. package/src/context/HyperElements.tsx +7 -6
  42. package/src/index.ts +9 -1
  43. package/src/native/NativeResponseMapper.ts +20 -4
  44. package/src/session/SavedPaymentMethods.ts +10 -2
  45. package/src/session/presentPaymentSheet.ts +7 -1
  46. package/src/views/PaymentElement.tsx +17 -8
@@ -545,6 +545,9 @@ class HyperFragment : ReactFragment() {
545
545
 
546
546
  override fun onDestroy() {
547
547
  try {
548
+ /* A fragment restored after recreation is a new instance, so pending callbacks can
549
+ never be fulfilled past this point; settle them instead of silently dropping. */
550
+ settlePendingCallbacks()
548
551
  unRegisterEventBus()
549
552
  callbacks.clear()
550
553
  onExit = null
@@ -554,6 +557,23 @@ class HyperFragment : ReactFragment() {
554
557
  }
555
558
  }
556
559
 
560
+ /* Failure payload mirrors StandardResult.Failed so the JS layer maps it to a failed result. */
561
+ private fun settlePendingCallbacks() {
562
+ val failure = StandardResult.Failed(
563
+ code = "SURFACE_LOST",
564
+ message = "The payment surface was destroyed before the flow completed",
565
+ error = Throwable("SURFACE_LOST")
566
+ ).toJSONString()
567
+ callbacks.values.forEach { hyperCallback ->
568
+ when (hyperCallback) {
569
+ is HyperCallback.Payment -> hyperCallback.fn(failure)
570
+ is HyperCallback.UpdateIntentInit -> hyperCallback.fn?.invoke()
571
+ is HyperCallback.UpdateIntentComplete -> hyperCallback.fn(failure)
572
+ is HyperCallback.ConfirmButtonTriggered -> Unit
573
+ }
574
+ }
575
+ }
576
+
557
577
  @SuppressLint("ClickableViewAccessibility")
558
578
  private fun fixScrollInterception(root: ViewGroup) {
559
579
  root.isNestedScrollingEnabled = true
@@ -537,6 +537,9 @@ class HyperFragment : ReactFragment() {
537
537
 
538
538
  override fun onDestroy() {
539
539
  try {
540
+ /* A fragment restored after recreation is a new instance, so pending callbacks can
541
+ never be fulfilled past this point; settle them instead of silently dropping. */
542
+ settlePendingCallbacks()
540
543
  unRegisterEventBus()
541
544
  callbacks.clear()
542
545
  onExit = null
@@ -546,6 +549,23 @@ class HyperFragment : ReactFragment() {
546
549
  }
547
550
  }
548
551
 
552
+ /* Failure payload mirrors StandardResult.Failed so the JS layer maps it to a failed result. */
553
+ private fun settlePendingCallbacks() {
554
+ val failure = StandardResult.Failed(
555
+ code = "SURFACE_LOST",
556
+ message = "The payment surface was destroyed before the flow completed",
557
+ error = Throwable("SURFACE_LOST")
558
+ ).toJSONString()
559
+ callbacks.values.forEach { hyperCallback ->
560
+ when (hyperCallback) {
561
+ is HyperCallback.Payment -> hyperCallback.fn(failure)
562
+ is HyperCallback.UpdateIntentInit -> hyperCallback.fn?.invoke()
563
+ is HyperCallback.UpdateIntentComplete -> hyperCallback.fn(failure)
564
+ is HyperCallback.ConfirmButtonTriggered -> Unit
565
+ }
566
+ }
567
+ }
568
+
549
569
  @SuppressLint("ClickableViewAccessibility")
550
570
  private fun fixScrollInterception(root: ViewGroup) {
551
571
  root.isNestedScrollingEnabled = true
@@ -135,13 +135,15 @@ public class NativeHyperswitchModuleImpl: NSObject {
135
135
  endpointsConfig = .commonEndpoint(common)
136
136
  }
137
137
 
138
- // Map environment string (Android uses "PROD"/"SANDBOX") to the Swift enum
138
+ // Map environment string (Android uses "PROD"/"SANDBOX"/"INTEG") to the Swift enum
139
139
  let envEnum: HyperswitchEnvironment?
140
140
  switch environment.uppercased() {
141
141
  case "PROD", "PRODUCTION":
142
142
  envEnum = .production
143
143
  case "SANDBOX":
144
144
  envEnum = .sandbox
145
+ case "INTEG":
146
+ envEnum = .integ
145
147
  default:
146
148
  envEnum = nil
147
149
  }
@@ -7,50 +7,49 @@
7
7
 
8
8
  import Foundation
9
9
  import PassKit
10
+ import UIKit
10
11
  import React
11
12
 
12
13
  internal class ApplePayHandler: NSObject {
13
14
 
14
15
  var paymentStatus: PKPaymentAuthorizationStatus? = .failure
15
16
  var callback: RCTResponseSenderBlock?
17
+ private var retainedSelf: ApplePayHandler?
16
18
 
17
19
  internal func startPayment(rnMessage: String, rnCallback: @escaping RCTResponseSenderBlock, presentCallback: RCTResponseSenderBlock?) {
18
20
 
19
21
  callback = rnCallback
22
+ retainedSelf = self
23
+ NativeLog.debug("wallet_launch_called", ["wallet": "apple_pay"])
20
24
  var requiredBillingContactFields: Set<PKContactField>?
21
25
  var requiredShippingContactFields: Set<PKContactField>?
22
26
 
23
27
  guard let dict = rnMessage.toJSON() as? [String: AnyObject] else {
24
28
  presentCallback?([])
25
- callback?([["status": "Failed", "message": "ApplePay: missing field payment_request_data"]])
26
- callback = nil
29
+ finish(["status": "Failed", "message": "ApplePay: missing field payment_request_data"])
27
30
  return
28
31
  }
29
32
 
30
33
  guard let payment_request_data = dict["payment_request_data"] else {
31
34
  presentCallback?([])
32
- callback?([["status": "Failed", "message": "ApplePay: missing field payment_request_data"]])
33
- callback = nil
35
+ finish(["status": "Failed", "message": "ApplePay: missing field payment_request_data"])
34
36
  return
35
37
  }
36
38
 
37
39
  guard let countryCode = payment_request_data["country_code"] as? String else {
38
40
  presentCallback?([])
39
- callback?([["status": "Failed", "message": "ApplePay: missing field country_code"]])
40
- callback = nil
41
+ finish(["status": "Failed", "message": "ApplePay: missing field country_code"])
41
42
  return
42
43
  }
43
44
 
44
45
  guard let currencyCode = payment_request_data["currency_code"] as? String else {
45
46
  presentCallback?([])
46
- callback?([["status": "Failed", "message": "ApplePay: missing field currency_code"]])
47
- callback = nil
47
+ finish(["status": "Failed", "message": "ApplePay: missing field currency_code"])
48
48
  return
49
49
  }
50
50
  guard let total = payment_request_data["total"] as? [String: AnyObject] else {
51
51
  presentCallback?([])
52
- callback?([["status": "Failed", "message": "ApplePay: missing field total"]])
53
- callback = nil
52
+ finish(["status": "Failed", "message": "ApplePay: missing field total"])
54
53
  return
55
54
  }
56
55
 
@@ -59,29 +58,25 @@ internal class ApplePayHandler: NSObject {
59
58
  let type = total["type"] as? String
60
59
  else {
61
60
  presentCallback?([])
62
- callback?([["status": "Failed", "message": "ApplePay: missing fields amount, label, type"]])
63
- callback = nil
61
+ finish(["status": "Failed", "message": "ApplePay: missing fields amount, label, type"])
64
62
  return
65
63
  }
66
64
 
67
65
  guard let merchant_capabilities_array = payment_request_data["merchant_capabilities"] as? [String] else {
68
66
  presentCallback?([])
69
- callback?([["status": "Failed", "message": "ApplePay: missing field merchant_capabilities"]])
70
- callback = nil
67
+ finish(["status": "Failed", "message": "ApplePay: missing field merchant_capabilities"])
71
68
  return
72
69
  }
73
70
 
74
71
  guard let merchantIdentifier = payment_request_data["merchant_identifier"] as? String else {
75
72
  presentCallback?([])
76
- callback?([["status": "Failed", "message": "ApplePay: missing field merchant_identifier"]])
77
- callback = nil
73
+ finish(["status": "Failed", "message": "ApplePay: missing field merchant_identifier"])
78
74
  return
79
75
  }
80
76
 
81
77
  guard let supported_networks_array = payment_request_data["supported_networks"] as? [String] else {
82
78
  presentCallback?([])
83
- callback?([["status": "Failed", "message": "ApplePay: missing field supported_networks"]])
84
- callback = nil
79
+ finish(["status": "Failed", "message": "ApplePay: missing field supported_networks"])
85
80
  return
86
81
  }
87
82
 
@@ -134,8 +129,7 @@ internal class ApplePayHandler: NSObject {
134
129
  PKPaymentAuthorizationViewController(paymentRequest: paymentRequest) != nil
135
130
  else {
136
131
  presentCallback?([])
137
- callback?([["status": "Failed", "message": "ApplePay: cannot make payments"]])
138
- callback = nil
132
+ finish(["status": "Failed", "message": "ApplePay: cannot make payments"])
139
133
  return
140
134
  }
141
135
  let paymentController = PKPaymentAuthorizationController(paymentRequest: paymentRequest)
@@ -145,11 +139,28 @@ internal class ApplePayHandler: NSObject {
145
139
  if presented {
146
140
  self.paymentStatus = nil
147
141
  } else {
148
- self.callback?([["status": "Failed", "message": "ApplePay: unable to present"]])
149
- self.callback = nil
142
+ NativeLog.error("wallet_present_failed", ["wallet": "apple_pay"])
143
+ self.finish(["status": "Failed", "message": "ApplePay: unable to present"])
150
144
  }
151
145
  })
152
146
  }
147
+
148
+ private func complete(_ result: [String: Any]) {
149
+ guard let callback else {
150
+ NativeLog.warning("wallet_result_dropped", ["wallet": "apple_pay", "status": result["status"] as? String])
151
+ return
152
+ }
153
+ self.callback = nil
154
+ NativeLog.debug("wallet_result_delivered", ["wallet": "apple_pay", "status": result["status"] as? String])
155
+ callback([result])
156
+ }
157
+
158
+ private func finish(_ result: [String: Any]? = nil) {
159
+ if let result {
160
+ complete(result)
161
+ }
162
+ retainedSelf = nil
163
+ }
153
164
  }
154
165
 
155
166
  /// Extension to conform to PKPaymentAuthorizationControllerDelegate
@@ -178,33 +189,33 @@ extension ApplePayHandler: PKPaymentAuthorizationControllerDelegate {
178
189
  default: paymentType = "unknown"
179
190
  }
180
191
 
181
- self.callback?(
192
+ complete(
182
193
  [
183
- [
184
- "status": "Success",
185
- "payment_data": dataString,
186
- "payment_method": [
187
- "type": paymentType,
188
- "network": payment.token.paymentMethod.network ?? "",
189
- "display_name": payment.token.paymentMethod.displayName ?? "",
190
- ],
191
- "transaction_identifier": payment.token.transactionIdentifier,
192
- "billing_contact": convertPKContactToDictionary(payment.billingContact),
193
- "shipping_contact": convertPKContactToDictionary(payment.shippingContact),
194
- ]
194
+ "status": "Success",
195
+ "payment_data": dataString,
196
+ "payment_method": [
197
+ "type": paymentType,
198
+ "network": payment.token.paymentMethod.network ?? "",
199
+ "display_name": payment.token.paymentMethod.displayName ?? "",
200
+ ],
201
+ "transaction_identifier": payment.token.transactionIdentifier,
202
+ "billing_contact": convertPKContactToDictionary(payment.billingContact),
203
+ "shipping_contact": convertPKContactToDictionary(payment.shippingContact),
195
204
  ]
196
205
  )
197
- self.callback = nil
198
206
  completion(PKPaymentAuthorizationResult(status: paymentStatus ?? .failure, errors: errors))
199
207
  }
200
208
  /// Handle completion of the payment authorization flow
201
209
  internal func paymentAuthorizationControllerDidFinish(_ controller: PKPaymentAuthorizationController) {
210
+ controller.delegate = nil
202
211
  controller.dismiss {
203
212
  DispatchQueue.main.async {
204
213
  if self.paymentStatus == .failure {
205
- self.callback?([["status": "Failed", "message": "ApplePay: payment failed"]])
214
+ self.finish(["status": "Failed", "message": "ApplePay: payment failed"])
206
215
  } else if self.paymentStatus == nil {
207
- self.callback?([["status": "Cancelled"]])
216
+ self.finish(["status": "Cancelled"])
217
+ } else {
218
+ self.finish()
208
219
  }
209
220
  }
210
221
  }
@@ -19,17 +19,13 @@ internal extension PaymentSheet {
19
19
  completion: @escaping (String) -> Void
20
20
  ) {
21
21
  self.completion = completion
22
+ NativeLog.debug("present_payment_sheet_called")
22
23
 
23
24
  let paymentSheetViewController = HyperUIViewController()
24
25
  paymentSheetViewController.paymentSheet = self
25
26
  paymentSheetViewController.modalPresentationStyle = .overFullScreen
26
27
  paymentSheetViewController.view = rootView
27
28
 
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
-
33
29
  presentingViewController.present(paymentSheetViewController, animated: false)
34
30
  }
35
31
 
@@ -55,27 +55,13 @@ public class HyperModuleImpl: NSObject {
55
55
  }
56
56
  }
57
57
 
58
- private let applePayPaymentHandler = ApplePayHandler()
59
58
  private let expressCheckoutHandler = ExpressCheckoutLauncher()
60
59
  private var presentCallback: RCTResponseSenderBlock? = nil
61
60
 
62
- /// Direct reference to the currently-presented PaymentSheet.
63
- /// Set by PaymentSheetView+UIKit when the sheet is presented; cleared in exitPaymentsheet.
64
- /// Avoids relying on bridge.uiManager.addUIBlock (which doesn't find Fabric views).
65
- private var activePaymentSheet: PaymentSheet?
66
- private weak var activePaymentSheetVC: HyperUIViewController?
67
-
68
61
  internal override init() {
69
62
  super.init()
70
63
  }
71
64
 
72
- // MARK: - Payment sheet registration (called from PaymentSheetView+UIKit)
73
-
74
- internal func registerPaymentSheet(_ sheet: PaymentSheet, vc: HyperUIViewController) {
75
- activePaymentSheet = sheet
76
- activePaymentSheetVC = vc
77
- }
78
-
79
65
  private func onMain(_ work: @escaping () -> Void) {
80
66
  if Thread.isMainThread {
81
67
  work()
@@ -130,7 +116,9 @@ public class HyperModuleImpl: NSObject {
130
116
  // MARK: - Apple Pay
131
117
 
132
118
  @objc public func launchApplePay(_ rnMessage: String, callback: @escaping RCTResponseSenderBlock) {
133
- applePayPaymentHandler.startPayment(rnMessage: rnMessage, rnCallback: callback, presentCallback: self.presentCallback)
119
+ let presentCallback = self.presentCallback
120
+ self.presentCallback = nil
121
+ ApplePayHandler().startPayment(rnMessage: rnMessage, rnCallback: callback, presentCallback: presentCallback)
134
122
  }
135
123
 
136
124
  @objc public func startApplePay(_ rnMessage: String, callback: @escaping RCTResponseSenderBlock) {
@@ -171,21 +159,24 @@ public class HyperModuleImpl: NSObject {
171
159
  // MARK: - Payment sheet exits
172
160
 
173
161
  @objc public func exitPaymentsheet(_ reactTag: NSNumber, result rnMessage: String, reset: Bool) {
174
- // Consume and clear so they can't be called a second time.
175
- let sheet = activePaymentSheet
176
- let vc = activePaymentSheetVC
177
- activePaymentSheet = nil
178
- activePaymentSheetVC = nil
179
-
180
- // Pass the raw JS result string directly — no PaymentResult conversion.
181
- // PaymentSheet.completion is (String) -> Void, so this goes straight to
182
- // the promise resolve in HyperswitchModule.presentPaymentSheet.
183
- sheet?.completion?(rnMessage)
184
-
185
- // Dismiss the VC, then release the rootView so the Fabric surface is torn down.
186
- vc?.dismiss(animated: false) {
187
- RNViewManager.sharedInstance.rootView?.removeFromSuperview()
188
- RNViewManager.sharedInstance.rootView = nil
162
+ withPaymentSheet(reactTag) { vc, sheet in
163
+ guard let sheet else {
164
+ NativeLog.error("exit_payment_sheet_no_sheet", ["rootTag": reactTag.intValue, "vcFound": vc != nil])
165
+ return
166
+ }
167
+ let completion = sheet.completion
168
+ sheet.completion = nil
169
+ guard let vc else {
170
+ NativeLog.warning("exit_payment_sheet_no_view_controller")
171
+ completion?(rnMessage)
172
+ return
173
+ }
174
+ vc.dismiss(animated: false) {
175
+ NativeLog.debug("exit_payment_sheet_resolved", ["completionPresent": completion != nil])
176
+ RNViewManager.sharedInstance.rootView?.removeFromSuperview()
177
+ RNViewManager.sharedInstance.rootView = nil
178
+ completion?(rnMessage)
179
+ }
189
180
  }
190
181
  }
191
182
 
@@ -489,10 +480,34 @@ public class HyperModuleImpl: NSObject {
489
480
 
490
481
  private func withPaymentSheet(_ rootTag: NSNumber, _ block: @escaping (UIViewController?, PaymentSheet?) -> Void) {
491
482
  DispatchQueue.main.async {
492
- let view = self.resolveView(forRootTag: rootTag)
493
- let vc = view?.reactViewController() as? HyperUIViewController
494
- let sheet = vc?.paymentSheet
495
- block(vc, sheet)
483
+ let vc: HyperUIViewController?
484
+ if let view = self.resolveView(forRootTag: rootTag) {
485
+ vc = view.reactViewController() as? HyperUIViewController
486
+ } else {
487
+ vc = HyperModuleImpl.topmostPaymentSheetController()
488
+ }
489
+ block(vc, vc?.paymentSheet)
496
490
  }
497
491
  }
492
+
493
+ private static func topmostPaymentSheetController() -> HyperUIViewController? {
494
+ let windows = UIApplication.shared.connectedScenes
495
+ .compactMap { $0 as? UIWindowScene }
496
+ .flatMap { $0.windows }
497
+ .sorted { $0.isKeyWindow && !$1.isKeyWindow }
498
+ for window in windows {
499
+ var found: HyperUIViewController?
500
+ var current = window.rootViewController
501
+ while let vc = current {
502
+ if let sheetVC = vc as? HyperUIViewController, sheetVC.paymentSheet != nil {
503
+ found = sheetVC
504
+ }
505
+ current = vc.presentedViewController
506
+ }
507
+ if let found {
508
+ return found
509
+ }
510
+ }
511
+ return nil
512
+ }
498
513
  }
@@ -22,7 +22,7 @@ internal class RNHeadlessManagerDelegate: RNFactoryDelegate {
22
22
  }
23
23
  }
24
24
 
25
- internal class RNHeadlessManager: RCTDefaultReactNativeFactoryDelegate {
25
+ internal class RNHeadlessManager: RCTDefaultReactNativeFactoryDelegate, ReactHostManager {
26
26
 
27
27
  internal var responseHandler: RNResponseHandler?
28
28
  internal var rootView: UIView?
@@ -42,6 +42,7 @@ internal class RNHeadlessManager: RCTDefaultReactNativeFactoryDelegate {
42
42
  return existing
43
43
  }
44
44
  delegate.dependencyProvider = RCTAppDependencyProvider()
45
+ delegate.manager = self
45
46
  let created = RCTReactNativeFactory(delegate: delegate)
46
47
  factory = created
47
48
  return created
@@ -102,17 +103,6 @@ internal class RNHeadlessManager: RCTDefaultReactNativeFactoryDelegate {
102
103
  }
103
104
  }
104
105
 
105
- // MARK: - RCTTurboModuleManagerDelegate
106
-
107
- @objc public func getModuleInstanceFromClass(_ moduleClass: AnyClass) -> AnyObject? {
108
- if String(describing: moduleClass) == "HyperModule",
109
- let shim = (moduleClass as? NSObject.Type)?.init() as? HyperModuleShim {
110
- hyperModule.attach(to: shim)
111
- return shim as AnyObject
112
- }
113
- return nil
114
- }
115
-
116
106
  public override func bridgelessEnabled() -> Bool {
117
107
  return true
118
108
  }