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

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,48 @@
1
+ require "json"
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, "package.json")))
4
+ folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32'
5
+
6
+ Pod::Spec.new do |s|
7
+ s.name = "YunoSdk"
8
+ s.version = package["version"]
9
+ s.summary = package["description"]
10
+ s.homepage = package["homepage"]
11
+ s.license = package["license"]
12
+ s.authors = package["author"]
13
+
14
+ s.platforms = { :ios => "14.0" }
15
+ s.source = { :git => "https://github.com/yuno-payments/yuno-sdk-react-native.git", :tag => "#{s.version}" }
16
+
17
+ s.source_files = "ios/**/*.{h,m,mm,swift}"
18
+
19
+ # Use install_modules_dependencies helper to install the dependencies if React Native version >=0.71.0.
20
+ # See https://github.com/facebook/react-native/blob/febf6b7f33fdb4904669f99d795eba4c0f95d7bf/scripts/cocoapods/new_architecture.rb#L79.
21
+ if respond_to?(:install_modules_dependencies, true)
22
+ install_modules_dependencies(s)
23
+ else
24
+ s.dependency "React-Core"
25
+
26
+ # Don't install the dependencies when we run `pod install` in the old architecture.
27
+ if ENV['RCT_NEW_ARCH_ENABLED'] == '1' then
28
+ s.compiler_flags = folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED=1"
29
+ s.pod_target_xcconfig = {
30
+ "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\"",
31
+ "OTHER_CPLUSPLUSFLAGS" => "-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1",
32
+ "CLANG_CXX_LANGUAGE_STANDARD" => "c++17"
33
+ }
34
+ s.dependency "React-Codegen"
35
+ s.dependency "RCT-Folly"
36
+ s.dependency "RCTRequired"
37
+ s.dependency "RCTTypeSafety"
38
+ s.dependency "ReactCommon/turbomodule/core"
39
+ end
40
+ end
41
+
42
+ # Yuno iOS SDK
43
+ s.dependency "YunoSDK", "2.7.1"
44
+
45
+ s.swift_version = '5.0'
46
+ s.static_framework = true
47
+ end
48
+
@@ -2,6 +2,7 @@ package com.yunosdkreactnative
2
2
 
3
3
  import android.app.Activity
4
4
  import android.content.Context
5
+ import android.util.Log
5
6
  import androidx.activity.ComponentActivity
6
7
  import com.facebook.react.bridge.*
7
8
  import com.facebook.react.modules.core.DeviceEventManagerModule
@@ -51,6 +52,14 @@ class YunoSdkModule(private val reactContext: ReactApplicationContext) :
51
52
  @Volatile
52
53
  private var lastOneTimeTokenInfo: OneTimeTokenModel? = null
53
54
 
55
+ // Store the last payment status to prevent stale status from previous flows
56
+ @Volatile
57
+ private var lastPaymentStatus: String? = null
58
+
59
+ // Flag to track if we're starting a fresh payment flow
60
+ @Volatile
61
+ private var isPaymentFlowCleared: Boolean = false
62
+
54
63
  // Gson instance for JSON serialization
55
64
  private val gson = Gson()
56
65
 
@@ -612,6 +621,24 @@ class YunoSdkModule(private val reactContext: ReactApplicationContext) :
612
621
  }
613
622
  }
614
623
 
624
+ /**
625
+ * Clears the last stored payment status.
626
+ * This should be called at the start of each new payment flow
627
+ * to prevent stale status from previous flows.
628
+ */
629
+ @ReactMethod
630
+ fun clearLastPaymentStatus(promise: Promise) {
631
+ try {
632
+ // Don't set to null - keep the last status to compare against stale events
633
+ // Just mark that we're starting a fresh flow
634
+ isPaymentFlowCleared = true
635
+ Log.d(TAG, "💫 Payment status cleared - fresh flow starting (last status was: $lastPaymentStatus)")
636
+ promise.resolve(true)
637
+ } catch (e: Exception) {
638
+ promise.reject("CLEAR_STATUS_ERROR", "Error clearing last payment status: ${e.message}", e)
639
+ }
640
+ }
641
+
615
642
  // Lifecycle Methods
616
643
  override fun onHostResume() {}
617
644
  override fun onHostPause() {}
@@ -651,6 +678,26 @@ class YunoSdkModule(private val reactContext: ReactApplicationContext) :
651
678
 
652
679
  internal fun sendPaymentStatusEvent(status: String?) {
653
680
  val convertedStatus = convertPaymentStatus(status)
681
+
682
+ // If we just cleared the status and this is the same status as before,
683
+ // it's a stale event from the native SDK - ignore it
684
+ if (isPaymentFlowCleared && convertedStatus == lastPaymentStatus) {
685
+ Log.d(TAG, "🚫 Ignoring stale payment status: $convertedStatus (from previous flow)")
686
+ isPaymentFlowCleared = false
687
+ lastPaymentStatus = null // Clear it now that we've ignored the stale event
688
+ return
689
+ }
690
+
691
+ // Reset the cleared flag after processing first real new event
692
+ if (isPaymentFlowCleared) {
693
+ Log.d(TAG, "✅ First new status after clear: $convertedStatus (previous was: $lastPaymentStatus)")
694
+ isPaymentFlowCleared = false
695
+ }
696
+
697
+ // Store the current status for future comparison
698
+ lastPaymentStatus = convertedStatus
699
+
700
+ Log.d(TAG, "✅ Emitting payment status: $convertedStatus")
654
701
  val params = Arguments.createMap().apply {
655
702
  putString("status", convertedStatus)
656
703
  }
@@ -774,4 +821,182 @@ class YunoSdkModule(private val reactContext: ReactApplicationContext) :
774
821
  }
775
822
  return array
776
823
  }
824
+
825
+ // ==================== HEADLESS PAYMENT FLOW ====================
826
+
827
+ /**
828
+ * Generate a one-time token (OTT) from collected payment data using the headless flow.
829
+ * This method mirrors the native SDK's generateToken() API.
830
+ *
831
+ * @param tokenCollectedData Map containing checkout_session, customer_session, and payment_method data
832
+ * @param checkoutSession The checkout session ID
833
+ * @param countryCode The country code for the payment
834
+ * @param promise Promise to resolve with token or error
835
+ */
836
+ @ReactMethod
837
+ fun generateToken(
838
+ tokenCollectedData: ReadableMap,
839
+ checkoutSession: String,
840
+ countryCode: String,
841
+ promise: Promise
842
+ ) {
843
+ try {
844
+ Log.d(TAG, "generateToken called with checkoutSession: $checkoutSession")
845
+
846
+ // Convert ReadableMap to TokenCollectedData using Gson
847
+ val gson = Gson()
848
+ val jsonString = convertReadableMapToJson(tokenCollectedData)
849
+ val collectedData = gson.fromJson(jsonString, TokenCollectedData::class.java)
850
+
851
+ // Create API client
852
+ val apiClient = Yuno.apiClientPayment(
853
+ checkoutSession = checkoutSession,
854
+ countryCode = countryCode,
855
+ context = reactContext
856
+ )
857
+
858
+ // Generate token
859
+ apiClient.generateToken(collectedData, reactContext).observe { result ->
860
+ try {
861
+ when {
862
+ result.containsKey("token") && result["token"] != null -> {
863
+ val token = result["token"] as String
864
+ Log.d(TAG, "✅ Token generated successfully")
865
+
866
+ val response = Arguments.createMap().apply {
867
+ putString("token", token)
868
+ }
869
+ promise.resolve(response)
870
+ }
871
+ result.containsKey("error") && result["error"] != null -> {
872
+ val error = result["error"] as String
873
+ Log.e(TAG, "❌ Token generation failed: $error")
874
+ promise.reject("TOKEN_GENERATION_ERROR", error)
875
+ }
876
+ else -> {
877
+ Log.e(TAG, "❌ Unknown response from token generation")
878
+ promise.reject("TOKEN_GENERATION_ERROR", "Unknown error occurred")
879
+ }
880
+ }
881
+ } catch (e: Exception) {
882
+ Log.e(TAG, "❌ Error processing token generation result: ${e.message}")
883
+ promise.reject("TOKEN_GENERATION_ERROR", e.message)
884
+ }
885
+ }
886
+ } catch (e: Exception) {
887
+ Log.e(TAG, "❌ Error in generateToken: ${e.message}")
888
+ promise.reject("TOKEN_GENERATION_ERROR", e.message)
889
+ }
890
+ }
891
+
892
+ /**
893
+ * Get the 3D Secure challenge URL for a checkout session.
894
+ * This method mirrors the native SDK's getThreeDSecureChallenge() API.
895
+ *
896
+ * @param checkoutSession The checkout session ID
897
+ * @param countryCode The country code for the payment
898
+ * @param promise Promise to resolve with URL or error
899
+ */
900
+ @ReactMethod
901
+ fun getThreeDSecureChallenge(
902
+ checkoutSession: String,
903
+ countryCode: String,
904
+ promise: Promise
905
+ ) {
906
+ try {
907
+ Log.d(TAG, "getThreeDSecureChallenge called with checkoutSession: $checkoutSession")
908
+
909
+ // Create API client
910
+ val apiClient = Yuno.apiClientPayment(
911
+ checkoutSession = checkoutSession,
912
+ countryCode = countryCode,
913
+ context = reactContext
914
+ )
915
+
916
+ // Get 3DS challenge
917
+ apiClient.getThreeDSecureChallenge(reactContext, checkoutSession).observe { result ->
918
+ try {
919
+ val response = Arguments.createMap().apply {
920
+ putString("type", result.type)
921
+ putString("data", result.data)
922
+ }
923
+
924
+ if (result.type == "URL") {
925
+ Log.d(TAG, "✅ 3DS Challenge URL retrieved successfully")
926
+ promise.resolve(response)
927
+ } else {
928
+ Log.e(TAG, "❌ 3DS Challenge failed: ${result.data}")
929
+ promise.reject("THREE_DS_ERROR", result.data)
930
+ }
931
+ } catch (e: Exception) {
932
+ Log.e(TAG, "❌ Error processing 3DS challenge result: ${e.message}")
933
+ promise.reject("THREE_DS_ERROR", e.message)
934
+ }
935
+ }
936
+ } catch (e: Exception) {
937
+ Log.e(TAG, "❌ Error in getThreeDSecureChallenge: ${e.message}")
938
+ promise.reject("THREE_DS_ERROR", e.message)
939
+ }
940
+ }
941
+
942
+ /**
943
+ * Helper function to convert ReadableMap to JSON string.
944
+ */
945
+ private fun convertReadableMapToJson(readableMap: ReadableMap): String {
946
+ val json = JSONObject()
947
+ val iterator = readableMap.keySetIterator()
948
+
949
+ while (iterator.hasNextKey()) {
950
+ val key = iterator.nextKey()
951
+ val value = when (readableMap.getType(key)) {
952
+ ReadableType.Boolean -> readableMap.getBoolean(key)
953
+ ReadableType.Number -> readableMap.getDouble(key)
954
+ ReadableType.String -> readableMap.getString(key)
955
+ ReadableType.Map -> convertReadableMapToJsonObject(readableMap.getMap(key)!!)
956
+ ReadableType.Array -> convertReadableArrayToJsonArray(readableMap.getArray(key)!!)
957
+ ReadableType.Null -> JSONObject.NULL
958
+ }
959
+ json.put(key, value)
960
+ }
961
+
962
+ return json.toString()
963
+ }
964
+
965
+ private fun convertReadableMapToJsonObject(readableMap: ReadableMap): JSONObject {
966
+ val json = JSONObject()
967
+ val iterator = readableMap.keySetIterator()
968
+
969
+ while (iterator.hasNextKey()) {
970
+ val key = iterator.nextKey()
971
+ val value = when (readableMap.getType(key)) {
972
+ ReadableType.Boolean -> readableMap.getBoolean(key)
973
+ ReadableType.Number -> readableMap.getDouble(key)
974
+ ReadableType.String -> readableMap.getString(key)
975
+ ReadableType.Map -> convertReadableMapToJsonObject(readableMap.getMap(key)!!)
976
+ ReadableType.Array -> convertReadableArrayToJsonArray(readableMap.getArray(key)!!)
977
+ ReadableType.Null -> JSONObject.NULL
978
+ }
979
+ json.put(key, value)
980
+ }
981
+
982
+ return json
983
+ }
984
+
985
+ private fun convertReadableArrayToJsonArray(readableArray: ReadableArray): JSONArray {
986
+ val json = JSONArray()
987
+
988
+ for (i in 0 until readableArray.size()) {
989
+ val value = when (readableArray.getType(i)) {
990
+ ReadableType.Boolean -> readableArray.getBoolean(i)
991
+ ReadableType.Number -> readableArray.getDouble(i)
992
+ ReadableType.String -> readableArray.getString(i)
993
+ ReadableType.Map -> convertReadableMapToJsonObject(readableArray.getMap(i))
994
+ ReadableType.Array -> convertReadableArrayToJsonArray(readableArray.getArray(i))
995
+ ReadableType.Null -> JSONObject.NULL
996
+ }
997
+ json.put(value)
998
+ }
999
+
1000
+ return json
1001
+ }
777
1002
  }
@@ -0,0 +1,10 @@
1
+ #import <React/RCTViewManager.h>
2
+
3
+ @interface RCT_EXTERN_MODULE(YunoPaymentMethodsViewManager, RCTViewManager)
4
+
5
+ // Export props
6
+ RCT_EXPORT_VIEW_PROPERTY(checkoutSession, NSString)
7
+ RCT_EXPORT_VIEW_PROPERTY(countryCode, NSString)
8
+
9
+ @end
10
+
@@ -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