@yuno-payments/yuno-sdk-react-native 1.0.16 → 1.0.17-rc.10

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.
@@ -0,0 +1,324 @@
1
+ import Foundation
2
+ import UIKit
3
+ import SwiftUI
4
+ import React
5
+ import YunoSDK
6
+
7
+ /**
8
+ * ViewManager for YunoPaymentMethodsView.
9
+ *
10
+ * This component embeds the native Yuno SDK payment methods list view
11
+ * into React Native, allowing developers to display payment methods in their app.
12
+ *
13
+ * Uses UIHostingController to bridge SwiftUI to UIKit for React Native.
14
+ */
15
+ @objc(YunoPaymentMethodsViewManager)
16
+ class YunoPaymentMethodsViewManager: RCTViewManager {
17
+
18
+ override func view() -> UIView! {
19
+ let view = YunoPaymentMethodsView()
20
+ // Tell React Native this view handles its own scrolling
21
+ view.clipsToBounds = true
22
+ return view
23
+ }
24
+
25
+ override static func requiresMainQueueSetup() -> Bool {
26
+ return true
27
+ }
28
+ }
29
+
30
+ /**
31
+ * Custom UIView that wraps the Yuno payment methods SwiftUI view using UIHostingController.
32
+ */
33
+ class YunoPaymentMethodsView: UIView, YunoPaymentFullDelegate {
34
+
35
+ private var hostingController: UIHostingController<AnyView>?
36
+ private var _checkoutSession: String?
37
+ private var _countryCode: String?
38
+
39
+ // YunoPaymentDelegate properties
40
+ var checkoutSession: String {
41
+ return _checkoutSession ?? ""
42
+ }
43
+
44
+ var countryCode: String {
45
+ return _countryCode ?? ""
46
+ }
47
+
48
+ var language: String? {
49
+ return nil // Use default language
50
+ }
51
+
52
+ var viewController: UIViewController? {
53
+ return self.findViewController()
54
+ }
55
+
56
+ override init(frame: CGRect) {
57
+ super.init(frame: frame)
58
+ setupView()
59
+ }
60
+
61
+ required init?(coder: NSCoder) {
62
+ super.init(coder: coder)
63
+ setupView()
64
+ }
65
+
66
+ private func setupView() {
67
+ self.backgroundColor = .clear
68
+ self.isUserInteractionEnabled = true
69
+ self.clipsToBounds = true
70
+
71
+ print("[YunoPaymentMethods] 🏗️ setupView called")
72
+ // Don't load immediately - wait for checkoutSession and countryCode props
73
+ // loadPaymentMethodsView() will be called from updateCheckoutSessionIfReady()
74
+ }
75
+
76
+ override func layoutSubviews() {
77
+ super.layoutSubviews()
78
+ print("[YunoPaymentMethods] 📐 layoutSubviews - frame: \(self.frame)")
79
+ // SwiftUI's Auto Layout constraints will handle the size
80
+ }
81
+
82
+
83
+ override func didMoveToWindow() {
84
+ super.didMoveToWindow()
85
+ if window != nil {
86
+ print("[YunoPaymentMethods] 🪟 View added to window, frame: \(self.frame)")
87
+ setNeedsLayout()
88
+ layoutIfNeeded()
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Loads the payment methods view asynchronously from the Yuno SDK.
94
+ * Uses UIHostingController to convert SwiftUI View to UIView.
95
+ *
96
+ * The key is to use a separate async function with explicit return type
97
+ * to help Swift's type inference select the correct overload.
98
+ */
99
+ private func loadPaymentMethodsView() {
100
+ print("[YunoPaymentMethods] 🔄 loadPaymentMethodsView called")
101
+ print("[YunoPaymentMethods] 📋 Current checkoutSession: \(self.checkoutSession)")
102
+ print("[YunoPaymentMethods] 🌍 Current countryCode: \(self.countryCode)")
103
+
104
+ Task { @MainActor in
105
+ // Get the SwiftUI view by calling the helper function
106
+ // that explicitly returns 'some View' type
107
+ print("[YunoPaymentMethods] 🎨 Getting SwiftUI view from Yuno SDK...")
108
+ let swiftUIView = await getYunoSwiftUIView()
109
+ print("[YunoPaymentMethods] ✅ SwiftUI view obtained")
110
+
111
+ // Wrap SwiftUI view in UIHostingController
112
+ let hostingController = UIHostingController(rootView: AnyView(swiftUIView))
113
+ hostingController.view.backgroundColor = .clear
114
+ self.hostingController = hostingController
115
+
116
+ guard let hostingView = hostingController.view else { return }
117
+
118
+ print("[YunoPaymentMethods] ➕ Adding SwiftUI hostingView")
119
+
120
+ // Add to view controller hierarchy for proper event handling
121
+ if let parentVC = self.findViewController() {
122
+ parentVC.addChild(hostingController)
123
+ hostingController.didMove(toParent: parentVC)
124
+ }
125
+
126
+ // Add as subview and pin to edges
127
+ self.addSubview(hostingView)
128
+ hostingView.translatesAutoresizingMaskIntoConstraints = false
129
+ NSLayoutConstraint.activate([
130
+ hostingView.topAnchor.constraint(equalTo: self.topAnchor),
131
+ hostingView.leadingAnchor.constraint(equalTo: self.leadingAnchor),
132
+ hostingView.trailingAnchor.constraint(equalTo: self.trailingAnchor),
133
+ hostingView.bottomAnchor.constraint(equalTo: self.bottomAnchor)
134
+ ])
135
+
136
+ print("[YunoPaymentMethods] ✅ Payment methods view loaded")
137
+ }
138
+ }
139
+
140
+ /**
141
+ * Helper function that explicitly returns SwiftUI View type.
142
+ * This helps Swift's type inference select the correct overload.
143
+ */
144
+ @MainActor
145
+ private func getYunoSwiftUIView() async -> some View {
146
+ // By declaring the return type as 'some View', Swift knows to call
147
+ // the SwiftUI version of getPaymentMethodViewAsync
148
+ await Yuno.getPaymentMethodViewAsync(delegate: self)
149
+ }
150
+
151
+ /**
152
+ * Updates the checkout session.
153
+ */
154
+ @objc func setCheckoutSession(_ checkoutSession: String) {
155
+ print("[YunoPaymentMethods] 📝 setCheckoutSession called with: \(checkoutSession)")
156
+ self._checkoutSession = checkoutSession
157
+ updateCheckoutSessionIfReady()
158
+ }
159
+
160
+ /**
161
+ * Updates the country code.
162
+ */
163
+ @objc func setCountryCode(_ countryCode: String) {
164
+ print("[YunoPaymentMethods] 📝 setCountryCode called with: \(countryCode)")
165
+ self._countryCode = countryCode
166
+ updateCheckoutSessionIfReady()
167
+ }
168
+
169
+ /**
170
+ * Updates the checkout session in Yuno SDK if both checkoutSession and countryCode are available.
171
+ * Once both are set, loads the payment methods view.
172
+ */
173
+ private func updateCheckoutSessionIfReady() {
174
+ guard let checkoutSession = _checkoutSession,
175
+ let countryCode = _countryCode,
176
+ !checkoutSession.isEmpty,
177
+ !countryCode.isEmpty else {
178
+ print("[YunoPaymentMethods] ⏳ Waiting for both checkoutSession and countryCode")
179
+ return
180
+ }
181
+
182
+ // Check if view is already loaded
183
+ if hostingController != nil {
184
+ print("[YunoPaymentMethods] ✅ View already loaded, skipping reload")
185
+ return
186
+ }
187
+
188
+ // The YunoPaymentDelegate protocol properties (checkoutSession, countryCode)
189
+ // are already implemented above and Yuno SDK will read them automatically
190
+ print("[YunoPaymentMethods] ✅✅ Checkout session ready: \(checkoutSession), country: \(countryCode)")
191
+ print("[YunoPaymentMethods] 🚀 Now loading payment methods view...")
192
+
193
+ // Now that we have both props, load the view
194
+ loadPaymentMethodsView()
195
+ }
196
+
197
+ // MARK: - YunoPaymentDelegate
198
+
199
+ func yunoCreatePayment(with token: String) {
200
+ print("[YunoPaymentMethods] yunoCreatePayment called with token: \(token)")
201
+
202
+ // Emit OTT token event to React Native
203
+ DispatchQueue.main.async {
204
+ if let bridge = RCTBridge.current() {
205
+ if let yunoModule = bridge.module(for: YunoSdk.self) as? YunoSdk {
206
+ yunoModule.sendOneTimeTokenEvent(token: token)
207
+ }
208
+ }
209
+ }
210
+ }
211
+
212
+ func yunoCreatePayment(with token: String, information: [String : Any]) {
213
+ print("[YunoPaymentMethods] yunoCreatePayment called with token and information")
214
+
215
+ // Emit OTT token event with full information to React Native
216
+ DispatchQueue.main.async {
217
+ if let bridge = RCTBridge.current() {
218
+ if let yunoModule = bridge.module(for: YunoSdk.self) as? YunoSdk {
219
+ yunoModule.sendOneTimeTokenEvent(token: token)
220
+ // The information parameter contains additional token details
221
+ // It's already sent via sendOneTimeTokenEvent which emits both events
222
+ }
223
+ }
224
+ }
225
+ }
226
+
227
+ func yunoPaymentResult(_ result: Yuno.Result) {
228
+ print("[YunoPaymentMethods] yunoPaymentResult: \(result)")
229
+
230
+ // Emit payment status event to React Native
231
+ let statusString: String
232
+ switch result {
233
+ case .succeeded:
234
+ statusString = "SUCCEEDED"
235
+ case .fail:
236
+ statusString = "FAILED"
237
+ case .reject:
238
+ statusString = "REJECTED"
239
+ case .processing:
240
+ statusString = "PROCESSING"
241
+ case .internalError:
242
+ statusString = "INTERNAL_ERROR"
243
+ case .userCancelled:
244
+ statusString = "CANCELLED_BY_USER"
245
+ @unknown default:
246
+ statusString = "UNKNOWN"
247
+ }
248
+
249
+ DispatchQueue.main.async {
250
+ if let bridge = RCTBridge.current() {
251
+ if let yunoModule = bridge.module(for: YunoSdk.self) as? YunoSdk {
252
+ yunoModule.sendPaymentStatusEvent(status: statusString)
253
+ }
254
+ }
255
+ }
256
+ }
257
+
258
+ // MARK: - YunoPaymentFullDelegate
259
+
260
+ func yunoDidSelect(paymentMethod: YunoSDK.PaymentMethodSelected) {
261
+ // Emit event to React Native
262
+ print("[YunoPaymentMethods] Payment method selected")
263
+ emitPaymentSelectedEvent(isSelected: true)
264
+ }
265
+
266
+ func yunoDidUnenrollSuccessfully(_ success: Bool) {
267
+ print("[YunoPaymentMethods] yunoDidUnenrollSuccessfully: \(success)")
268
+ }
269
+
270
+ func yunoUpdatePaymentMethodsViewHeight(_ height: CGFloat) {
271
+ // Update view height if needed
272
+ print("[YunoPaymentMethods] yunoUpdatePaymentMethodsViewHeight: \(height)")
273
+ }
274
+
275
+ // MARK: - Event Emission
276
+
277
+ /**
278
+ * Emits a payment method selected event to React Native.
279
+ */
280
+ private func emitPaymentSelectedEvent(isSelected: Bool) {
281
+ // Send event through YunoSdk module
282
+ DispatchQueue.main.async {
283
+ if let bridge = RCTBridge.current() {
284
+ if let yunoModule = bridge.module(for: YunoSdk.self) as? YunoSdk {
285
+ yunoModule.sendEvent(
286
+ withName: "onPaymentMethodSelected",
287
+ body: ["isSelected": isSelected]
288
+ )
289
+ }
290
+ }
291
+ }
292
+ }
293
+
294
+ /**
295
+ * Emits an error event to React Native.
296
+ */
297
+ private func emitErrorEvent(message: String) {
298
+ DispatchQueue.main.async {
299
+ if let bridge = RCTBridge.current() {
300
+ if let yunoModule = bridge.module(for: YunoSdk.self) as? YunoSdk {
301
+ yunoModule.sendEvent(
302
+ withName: "onPaymentMethodError",
303
+ body: ["message": message]
304
+ )
305
+ }
306
+ }
307
+ }
308
+ }
309
+ }
310
+
311
+ // MARK: - UIView extension to find view controller
312
+
313
+ extension UIView {
314
+ func findViewController() -> UIViewController? {
315
+ if let nextResponder = self.next as? UIViewController {
316
+ return nextResponder
317
+ } else if let nextResponder = self.next as? UIView {
318
+ return nextResponder.findViewController()
319
+ } else {
320
+ return nil
321
+ }
322
+ }
323
+ }
324
+
package/ios/YunoSdk.m CHANGED
@@ -33,7 +33,9 @@ RCT_EXTERN_METHOD(
33
33
  )
34
34
 
35
35
  RCT_EXTERN_METHOD(
36
- continuePayment:(BOOL)showPaymentStatus
36
+ continuePayment:(NSString *)checkoutSession
37
+ countryCode:(NSString *)countryCode
38
+ showPaymentStatus:(BOOL)showPaymentStatus
37
39
  resolver:(RCTPromiseResolveBlock)resolver
38
40
  rejecter:(RCTPromiseRejectBlock)rejecter
39
41
  )
@@ -56,10 +58,45 @@ RCT_EXTERN_METHOD(
56
58
  rejecter:(RCTPromiseRejectBlock)rejecter
57
59
  )
58
60
 
59
- + (BOOL)requiresMainQueueSetup
60
- {
61
- return YES;
62
- }
61
+ RCT_EXTERN_METHOD(
62
+ getLastOneTimeToken:(RCTPromiseResolveBlock)resolver
63
+ rejecter:(RCTPromiseRejectBlock)rejecter
64
+ )
65
+
66
+ RCT_EXTERN_METHOD(
67
+ getLastOneTimeTokenInfo:(RCTPromiseResolveBlock)resolver
68
+ rejecter:(RCTPromiseRejectBlock)rejecter
69
+ )
70
+
71
+ RCT_EXTERN_METHOD(
72
+ clearLastOneTimeToken:(RCTPromiseResolveBlock)resolver
73
+ rejecter:(RCTPromiseRejectBlock)rejecter
74
+ )
75
+
76
+ RCT_EXTERN_METHOD(
77
+ clearLastPaymentStatus:(RCTPromiseResolveBlock)resolver
78
+ rejecter:(RCTPromiseRejectBlock)rejecter
79
+ )
80
+
81
+ RCT_EXTERN_METHOD(addListener:(NSString *)eventName)
82
+
83
+ RCT_EXTERN_METHOD(removeListeners:(double)count)
84
+
85
+ // Headless Payment Flow
86
+ RCT_EXTERN_METHOD(
87
+ generateToken:(NSDictionary *)tokenCollectedData
88
+ checkoutSession:(NSString *)checkoutSession
89
+ countryCode:(NSString *)countryCode
90
+ resolver:(RCTPromiseResolveBlock)resolver
91
+ rejecter:(RCTPromiseRejectBlock)rejecter
92
+ )
93
+
94
+ RCT_EXTERN_METHOD(
95
+ getThreeDSecureChallenge:(NSString *)checkoutSession
96
+ countryCode:(NSString *)countryCode
97
+ resolver:(RCTPromiseResolveBlock)resolver
98
+ rejecter:(RCTPromiseRejectBlock)rejecter
99
+ )
63
100
 
64
101
  @end
65
102