@yuno-payments/yuno-sdk-react-native 1.0.16

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 (92) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +621 -0
  3. package/android/build.gradle +131 -0
  4. package/android/gradle.properties +5 -0
  5. package/android/src/main/AndroidManifest.xml +4 -0
  6. package/android/src/main/java/com/yunosdkreactnative/YunoPaymentMethodsViewManager.kt +194 -0
  7. package/android/src/main/java/com/yunosdkreactnative/YunoSdkModule.kt +777 -0
  8. package/android/src/main/java/com/yunosdkreactnative/YunoSdkPackage.kt +24 -0
  9. package/ios/YunoSdk.m +65 -0
  10. package/ios/YunoSdk.podspec +48 -0
  11. package/ios/YunoSdk.swift +442 -0
  12. package/lib/commonjs/YunoPaymentMethods.js +145 -0
  13. package/lib/commonjs/YunoPaymentMethods.js.map +1 -0
  14. package/lib/commonjs/YunoSdk.js +455 -0
  15. package/lib/commonjs/YunoSdk.js.map +1 -0
  16. package/lib/commonjs/core/enums/CardFlow.js +26 -0
  17. package/lib/commonjs/core/enums/CardFlow.js.map +1 -0
  18. package/lib/commonjs/core/enums/YunoLanguage.js +58 -0
  19. package/lib/commonjs/core/enums/YunoLanguage.js.map +1 -0
  20. package/lib/commonjs/core/enums/YunoStatus.js +40 -0
  21. package/lib/commonjs/core/enums/YunoStatus.js.map +1 -0
  22. package/lib/commonjs/core/enums/index.js +27 -0
  23. package/lib/commonjs/core/enums/index.js.map +1 -0
  24. package/lib/commonjs/core/index.js +28 -0
  25. package/lib/commonjs/core/index.js.map +1 -0
  26. package/lib/commonjs/core/types/AndroidConfig.js +2 -0
  27. package/lib/commonjs/core/types/AndroidConfig.js.map +1 -0
  28. package/lib/commonjs/core/types/EnrollmentArguments.js +2 -0
  29. package/lib/commonjs/core/types/EnrollmentArguments.js.map +1 -0
  30. package/lib/commonjs/core/types/IosConfig.js +2 -0
  31. package/lib/commonjs/core/types/IosConfig.js.map +1 -0
  32. package/lib/commonjs/core/types/OneTimeTokenInfo.js +2 -0
  33. package/lib/commonjs/core/types/OneTimeTokenInfo.js.map +1 -0
  34. package/lib/commonjs/core/types/SeamlessArguments.js +6 -0
  35. package/lib/commonjs/core/types/SeamlessArguments.js.map +1 -0
  36. package/lib/commonjs/core/types/StartPayment.js +2 -0
  37. package/lib/commonjs/core/types/StartPayment.js.map +1 -0
  38. package/lib/commonjs/core/types/YunoConfig.js +6 -0
  39. package/lib/commonjs/core/types/YunoConfig.js.map +1 -0
  40. package/lib/commonjs/core/types/index.js +2 -0
  41. package/lib/commonjs/core/types/index.js.map +1 -0
  42. package/lib/commonjs/index.js +36 -0
  43. package/lib/commonjs/index.js.map +1 -0
  44. package/lib/module/YunoPaymentMethods.js +138 -0
  45. package/lib/module/YunoPaymentMethods.js.map +1 -0
  46. package/lib/module/YunoSdk.js +448 -0
  47. package/lib/module/YunoSdk.js.map +1 -0
  48. package/lib/module/core/enums/CardFlow.js +20 -0
  49. package/lib/module/core/enums/CardFlow.js.map +1 -0
  50. package/lib/module/core/enums/YunoLanguage.js +52 -0
  51. package/lib/module/core/enums/YunoLanguage.js.map +1 -0
  52. package/lib/module/core/enums/YunoStatus.js +34 -0
  53. package/lib/module/core/enums/YunoStatus.js.map +1 -0
  54. package/lib/module/core/enums/index.js +4 -0
  55. package/lib/module/core/enums/index.js.map +1 -0
  56. package/lib/module/core/index.js +3 -0
  57. package/lib/module/core/index.js.map +1 -0
  58. package/lib/module/core/types/AndroidConfig.js +2 -0
  59. package/lib/module/core/types/AndroidConfig.js.map +1 -0
  60. package/lib/module/core/types/EnrollmentArguments.js +2 -0
  61. package/lib/module/core/types/EnrollmentArguments.js.map +1 -0
  62. package/lib/module/core/types/IosConfig.js +2 -0
  63. package/lib/module/core/types/IosConfig.js.map +1 -0
  64. package/lib/module/core/types/OneTimeTokenInfo.js +2 -0
  65. package/lib/module/core/types/OneTimeTokenInfo.js.map +1 -0
  66. package/lib/module/core/types/SeamlessArguments.js +2 -0
  67. package/lib/module/core/types/SeamlessArguments.js.map +1 -0
  68. package/lib/module/core/types/StartPayment.js +2 -0
  69. package/lib/module/core/types/StartPayment.js.map +1 -0
  70. package/lib/module/core/types/YunoConfig.js +2 -0
  71. package/lib/module/core/types/YunoConfig.js.map +1 -0
  72. package/lib/module/core/types/index.js +2 -0
  73. package/lib/module/core/types/index.js.map +1 -0
  74. package/lib/module/index.js +4 -0
  75. package/lib/module/index.js.map +1 -0
  76. package/package.json +142 -0
  77. package/src/YunoPaymentMethods.tsx +196 -0
  78. package/src/YunoSdk.ts +518 -0
  79. package/src/core/enums/CardFlow.ts +18 -0
  80. package/src/core/enums/YunoLanguage.ts +50 -0
  81. package/src/core/enums/YunoStatus.ts +32 -0
  82. package/src/core/enums/index.ts +3 -0
  83. package/src/core/index.ts +2 -0
  84. package/src/core/types/AndroidConfig.ts +17 -0
  85. package/src/core/types/EnrollmentArguments.ts +32 -0
  86. package/src/core/types/IosConfig.ts +17 -0
  87. package/src/core/types/OneTimeTokenInfo.ts +207 -0
  88. package/src/core/types/SeamlessArguments.ts +42 -0
  89. package/src/core/types/StartPayment.ts +59 -0
  90. package/src/core/types/YunoConfig.ts +55 -0
  91. package/src/core/types/index.ts +7 -0
  92. package/src/index.ts +17 -0
@@ -0,0 +1,777 @@
1
+ package com.yunosdkreactnative
2
+
3
+ import android.app.Activity
4
+ import android.content.Context
5
+ import androidx.activity.ComponentActivity
6
+ import com.facebook.react.bridge.*
7
+ import com.facebook.react.modules.core.DeviceEventManagerModule
8
+ import com.yuno.sdk.Yuno
9
+ import com.yuno.sdk.YunoConfig
10
+ import com.yuno.sdk.YunoLanguage
11
+ import com.yuno.sdk.enrollment.*
12
+ import com.yuno.sdk.payments.*
13
+ import com.yuno.presentation.core.components.PaymentSelected
14
+ import com.yuno.presentation.core.card.CardFormType
15
+ import com.yuno.payments.features.payment.models.OneTimeTokenModel
16
+ import com.google.gson.Gson
17
+ import org.json.JSONObject
18
+ import org.json.JSONArray
19
+
20
+ /**
21
+ * Yuno SDK React Native Module for Android
22
+ *
23
+ * This module bridges the Yuno native Android SDK to React Native,
24
+ * providing payment and enrollment functionalities.
25
+ *
26
+ * IMPORTANT: Your MainActivity MUST call YunoSdkModule.registerYunoCallbacks(this)
27
+ * in onCreate() to register the required ActivityResultLaunchers.
28
+ */
29
+ class YunoSdkModule(private val reactContext: ReactApplicationContext) :
30
+ ReactContextBaseJavaModule(reactContext), LifecycleEventListener {
31
+
32
+ private var isInitialized = false
33
+
34
+ companion object {
35
+ const val NAME = "YunoSdk"
36
+ private const val TAG = "YunoSdk"
37
+
38
+ // Singleton instance
39
+ @Volatile
40
+ private var instance: YunoSdkModule? = null
41
+
42
+ // Track if SDK was initialized by Activity before module loaded
43
+ @Volatile
44
+ private var sdkInitializedByActivity = false
45
+
46
+ // Store the last OTT token received
47
+ @Volatile
48
+ private var lastOneTimeToken: String? = null
49
+
50
+ // Store the last OTT token info received (full model)
51
+ @Volatile
52
+ private var lastOneTimeTokenInfo: OneTimeTokenModel? = null
53
+
54
+ // Gson instance for JSON serialization
55
+ private val gson = Gson()
56
+
57
+ /**
58
+ * Clear the last OTT tokens (token and tokenInfo).
59
+ * This should be called at the start of each new payment/enrollment flow
60
+ * to prevent contamination from previous flows.
61
+ */
62
+ @JvmStatic
63
+ fun clearLastOTT() {
64
+ lastOneTimeToken = null
65
+ lastOneTimeTokenInfo = null
66
+ }
67
+
68
+ fun getInstance(): YunoSdkModule? = instance
69
+
70
+ /**
71
+ * Initialize Yuno SDK with API key, application context and configuration.
72
+ *
73
+ * IMPORTANT: This should be called from MainActivity to ensure
74
+ * the SDK is initialized with the application context (not activity context).
75
+ *
76
+ * @param applicationContext The application context
77
+ * @param apiKey The Yuno API key
78
+ * @param language Optional language code (e.g., "pt-BR", "es", "en")
79
+ * @param cardType Card flow type ("ONE_STEP" or "STEP_BY_STEP")
80
+ * @param savedCardEnable Whether to enable saved card functionality
81
+ */
82
+ @JvmStatic
83
+ fun initialize(
84
+ applicationContext: android.content.Context,
85
+ apiKey: String,
86
+ language: String? = null,
87
+ cardType: String = "ONE_STEP",
88
+ savedCardEnable: Boolean = false
89
+ ) {
90
+ try {
91
+ // Map cardType string to CardFormType enum
92
+ val cardFormType = when (cardType.uppercase()) {
93
+ "TWO_STEPS", "STEP_BY_STEP" -> CardFormType.STEP_BY_STEP
94
+ else -> CardFormType.ONE_STEP
95
+ }
96
+
97
+ // Map language string to YunoLanguage enum (if provided)
98
+ val yunoLanguage = language?.let {
99
+ try {
100
+ when {
101
+ it.startsWith("en", ignoreCase = true) -> YunoLanguage.ENGLISH
102
+ it.startsWith("es", ignoreCase = true) -> YunoLanguage.SPANISH
103
+ it.startsWith("pt", ignoreCase = true) -> YunoLanguage.PORTUGUESE
104
+ it.startsWith("id", ignoreCase = true) -> YunoLanguage.INDONESIAN
105
+ it.startsWith("ms", ignoreCase = true) || it.startsWith("my", ignoreCase = true) -> YunoLanguage.MALAYSIAN
106
+ it.startsWith("fr", ignoreCase = true) -> YunoLanguage.FRENCH
107
+ it.startsWith("pl", ignoreCase = true) -> YunoLanguage.POLISH
108
+ it.startsWith("it", ignoreCase = true) -> YunoLanguage.ITALIAN
109
+ it.startsWith("de", ignoreCase = true) -> YunoLanguage.GERMAN
110
+ it.startsWith("ru", ignoreCase = true) -> YunoLanguage.RUSSIAN
111
+ it.startsWith("tr", ignoreCase = true) -> YunoLanguage.TURKISH
112
+ it.startsWith("nl", ignoreCase = true) -> YunoLanguage.DUTCH
113
+ it.startsWith("sv", ignoreCase = true) -> YunoLanguage.SWEDISH
114
+ it.startsWith("th", ignoreCase = true) -> YunoLanguage.THAI
115
+ it.startsWith("fil", ignoreCase = true) -> YunoLanguage.FILIPINO
116
+ it.startsWith("vi", ignoreCase = true) -> YunoLanguage.VIETNAMESE
117
+ it.startsWith("zh-cn", ignoreCase = true) -> YunoLanguage.CHINESE_SIMPLIFIED
118
+ it.startsWith("zh-tw", ignoreCase = true) -> YunoLanguage.CHINESE_TRADITIONAL
119
+ else -> null
120
+ }
121
+ } catch (e: Exception) {
122
+ null
123
+ }
124
+ }
125
+
126
+ // Create YunoConfig
127
+ val config = YunoConfig(
128
+ cardFlow = cardFormType,
129
+ saveCardEnabled = savedCardEnable,
130
+ language = yunoLanguage
131
+ )
132
+
133
+
134
+ // Initialize with config
135
+ Yuno.initialize(
136
+ applicationContext = applicationContext,
137
+ apiKey = apiKey,
138
+ config = config
139
+ )
140
+
141
+ // Mark that SDK was initialized by Activity
142
+ sdkInitializedByActivity = true
143
+
144
+ // Also mark instance if it's already loaded
145
+ instance?.let {
146
+ it.isInitialized = true
147
+ }
148
+ } catch (e: Exception) {
149
+ throw e
150
+ }
151
+ }
152
+
153
+ /**
154
+ * Register Yuno SDK callbacks for an activity.
155
+ *
156
+ * IMPORTANT: This MUST be called from YunoActivity.onCreate() BEFORE super.onCreate()
157
+ * This is required by Android's registerForActivityResult() API.
158
+ *
159
+ * The SDK must be already initialized before calling this method.
160
+ *
161
+ * NOTE: This method can be called multiple times (e.g. when "Don't Keep Activities" is enabled
162
+ * and the activity is recreated). Android handles multiple registrations correctly by replacing
163
+ * the previous launcher.
164
+ *
165
+ * @param activity The ComponentActivity instance (usually YunoActivity)
166
+ */
167
+ @JvmStatic
168
+ fun registerYunoCallbacks(activity: ComponentActivity) {
169
+ try {
170
+
171
+ // Register payment callbacks
172
+ // Note: Android allows registering multiple times - the last registration wins
173
+ activity.startCheckout(
174
+ callbackOTT = { token ->
175
+ notifyOneTimeToken(token)
176
+ },
177
+ callbackPaymentState = { state ->
178
+ notifyPaymentState(state)
179
+ }
180
+ )
181
+
182
+ // Register enrollment callbacks
183
+ activity.initEnrollment { enrollmentState ->
184
+ notifyEnrollmentState(enrollmentState)
185
+ }
186
+
187
+
188
+ } catch (e: Exception) {
189
+ throw e
190
+ }
191
+ }
192
+
193
+ @JvmStatic
194
+ fun notifyOneTimeToken(token: String?) {
195
+
196
+ // ALWAYS store the token, even if React Native is not ready
197
+ lastOneTimeToken = token
198
+
199
+ if (instance == null) {
200
+ } else {
201
+ instance?.sendOneTimeTokenEvent(token)
202
+ }
203
+ }
204
+
205
+ @JvmStatic
206
+ fun notifyPaymentState(state: String?) {
207
+ if (instance == null) {
208
+ } else {
209
+ instance?.sendPaymentStatusEvent(state)
210
+ }
211
+ }
212
+
213
+ @JvmStatic
214
+ fun notifyEnrollmentState(state: String?) {
215
+ if (instance == null) {
216
+ } else {
217
+ instance?.sendEnrollmentStatusEvent(state)
218
+ }
219
+ }
220
+ }
221
+
222
+ init {
223
+ reactContext.addLifecycleEventListener(this)
224
+ instance = this
225
+
226
+
227
+ // Check if SDK was already initialized by Activity before module loaded
228
+ if (sdkInitializedByActivity) {
229
+ this.isInitialized = true
230
+ } else {
231
+ }
232
+ }
233
+
234
+ override fun getName(): String = NAME
235
+
236
+ /**
237
+ * Initialize the Yuno SDK with configuration.
238
+ *
239
+ * Note: If the SDK was already initialized in MainActivity.onCreate() via
240
+ * registerYunoCallbacks(), this will be a no-op.
241
+ */
242
+ /**
243
+ * Initialize the Yuno SDK from JavaScript/React Native.
244
+ * This method extracts configuration from the provided maps and calls
245
+ * the static initialize() method from the companion object.
246
+ */
247
+ @ReactMethod
248
+ fun initialize(
249
+ apiKey: String,
250
+ countryCode: String,
251
+ config: ReadableMap,
252
+ iosConfig: ReadableMap,
253
+ androidConfig: ReadableMap,
254
+ promise: Promise
255
+ ) {
256
+ try {
257
+ if (this.isInitialized) {
258
+ promise.resolve(true)
259
+ return
260
+ }
261
+
262
+ if (apiKey.isEmpty()) {
263
+ promise.reject("INVALID_API_KEY", "API key cannot be empty")
264
+ return
265
+ }
266
+
267
+ // Extract configuration from config map
268
+ val language = if (config.hasKey("language")) config.getString("language") else null
269
+ val cardType = if (config.hasKey("cardType")) config.getString("cardType") else null
270
+ val savedCardEnable = if (config.hasKey("saveCardEnabled")) config.getBoolean("saveCardEnabled") else false
271
+
272
+
273
+ // Call the static initialize method
274
+ initialize(
275
+ applicationContext = reactContext.applicationContext,
276
+ apiKey = apiKey,
277
+ language = language,
278
+ cardType = cardType ?: "ONE_STEP",
279
+ savedCardEnable = savedCardEnable
280
+ )
281
+
282
+ this.isInitialized = true
283
+ promise.resolve(true)
284
+
285
+ } catch (e: Exception) {
286
+ promise.reject("YUNO_SDK_ERROR", "Error initializing Yuno SDK: ${e.message}", e)
287
+ }
288
+ }
289
+
290
+ /**
291
+ * Start enrollment payment flow.
292
+ */
293
+ @ReactMethod
294
+ fun enrollmentPayment(params: ReadableMap, promise: Promise) {
295
+ if (!checkInitialized(promise)) return
296
+ clearLastOTT() // 🧹 Clear any previous OTT tokens before starting new flow
297
+
298
+ try {
299
+ val customerSession = params.getString("customerSession") ?: run {
300
+ promise.reject("INVALID_PARAMS", "customerSession is required")
301
+ return
302
+ }
303
+
304
+ val countryCode = params.getString("countryCode") ?: run {
305
+ promise.reject("INVALID_PARAMS", "countryCode is required")
306
+ return
307
+ }
308
+
309
+ val showEnrollmentStatus = if (params.hasKey("showEnrollmentStatus")) {
310
+ params.getBoolean("showEnrollmentStatus")
311
+ } else {
312
+ true
313
+ }
314
+
315
+ val activity = currentActivity ?: run {
316
+ promise.reject("NO_ACTIVITY", "Current activity is not available")
317
+ return
318
+ }
319
+
320
+
321
+ // Start enrollment
322
+ activity.startEnrollment(
323
+ customerSession = customerSession,
324
+ countryCode = countryCode,
325
+ showEnrollmentStatus = showEnrollmentStatus,
326
+ callbackEnrollmentState = { enrollmentState ->
327
+ sendEnrollmentStatusEvent(enrollmentState)
328
+ }
329
+ )
330
+
331
+ promise.resolve(true)
332
+
333
+ } catch (e: Exception) {
334
+ promise.reject("ENROLLMENT_ERROR", "Error starting enrollment: ${e.message}", e)
335
+ }
336
+ }
337
+
338
+ /**
339
+ * Start payment lite flow.
340
+ */
341
+ @ReactMethod
342
+ fun startPaymentLite(params: ReadableMap, countryCode: String, promise: Promise) {
343
+ if (!checkInitialized(promise)) return
344
+ clearLastOTT() // 🧹 Clear any previous OTT tokens before starting new flow
345
+
346
+ try {
347
+ val checkoutSession = params.getString("checkoutSession") ?: run {
348
+ promise.reject("INVALID_PARAMS", "checkoutSession is required")
349
+ return
350
+ }
351
+
352
+ val showPaymentStatus = if (params.hasKey("showPaymentStatus")) {
353
+ params.getBoolean("showPaymentStatus")
354
+ } else {
355
+ true
356
+ }
357
+
358
+ val methodSelected = params.getMap("methodSelected")
359
+ val vaultedToken = methodSelected?.getString("vaultedToken")
360
+ val paymentMethodType = methodSelected?.getString("paymentMethodType") ?: run {
361
+ promise.reject("INVALID_PARAMS", "paymentMethodType is required")
362
+ return
363
+ }
364
+
365
+ val activity = currentActivity ?: run {
366
+ promise.reject("NO_ACTIVITY", "Current activity is not available")
367
+ return
368
+ }
369
+
370
+
371
+ // Update checkout session
372
+ activity.updateCheckoutSession(
373
+ checkoutSession = checkoutSession,
374
+ countryCode = countryCode
375
+ )
376
+
377
+ // Start payment lite
378
+ activity.startPaymentLite(
379
+ paymentSelected = PaymentSelected(
380
+ paymentMethodType = paymentMethodType,
381
+ vaultedToken = vaultedToken
382
+ ),
383
+ showPaymentStatus = showPaymentStatus,
384
+ callbackOTT = { token ->
385
+ notifyOneTimeToken(token)
386
+ },
387
+ callBackTokenWithInformation = { tokenModel ->
388
+ // Send simple OTT event
389
+ tokenModel?.token?.let { notifyOneTimeToken(it) }
390
+ // Send full OTT info event with card and customer data
391
+ sendOneTimeTokenInfoEvent(tokenModel)
392
+ }
393
+ )
394
+
395
+ promise.resolve(true)
396
+
397
+ } catch (e: Exception) {
398
+ promise.reject("PAYMENT_LITE_ERROR", "Error starting payment lite: ${e.message}", e)
399
+ }
400
+ }
401
+
402
+ /**
403
+ * Start full payment flow.
404
+ */
405
+ @ReactMethod
406
+ fun startPayment(showPaymentStatus: Boolean, promise: Promise) {
407
+ if (!checkInitialized(promise)) return
408
+ clearLastOTT() // 🧹 Clear any previous OTT tokens before starting new flow
409
+
410
+ try {
411
+ val activity = currentActivity ?: run {
412
+ promise.reject("NO_ACTIVITY", "Current activity is not available")
413
+ return
414
+ }
415
+
416
+
417
+ // Start payment
418
+ activity.startPayment(
419
+ showPaymentStatus = showPaymentStatus,
420
+ callbackOTT = { token ->
421
+ notifyOneTimeToken(token)
422
+ },
423
+ callBackTokenWithInformation = { tokenModel ->
424
+ // Send simple OTT event
425
+ tokenModel?.token?.let { notifyOneTimeToken(it) }
426
+ // Send full OTT info event with card and customer data
427
+ sendOneTimeTokenInfoEvent(tokenModel)
428
+ }
429
+ )
430
+
431
+ promise.resolve(true)
432
+
433
+ } catch (e: Exception) {
434
+ promise.reject("START_PAYMENT_ERROR", "Error starting payment: ${e.message}", e)
435
+ }
436
+ }
437
+
438
+ /**
439
+ * Continue a previously started payment.
440
+ */
441
+ @ReactMethod
442
+ fun continuePayment(checkoutSession: String, countryCode: String, showPaymentStatus: Boolean, promise: Promise) {
443
+ if (!checkInitialized(promise)) return
444
+
445
+ try {
446
+ val activity = currentActivity ?: run {
447
+ promise.reject("NO_ACTIVITY", "Current activity is not available")
448
+ return
449
+ }
450
+
451
+
452
+ // Continue payment
453
+ activity.continuePayment(
454
+ showPaymentStatus = showPaymentStatus,
455
+ checkoutSession = checkoutSession,
456
+ countryCode = countryCode,
457
+ callbackPaymentState = { paymentState ->
458
+ sendPaymentStatusEvent(paymentState)
459
+ }
460
+ )
461
+
462
+ promise.resolve(true)
463
+
464
+ } catch (e: Exception) {
465
+ promise.reject("CONTINUE_PAYMENT_ERROR", "Error continuing payment: ${e.message}", e)
466
+ }
467
+ }
468
+
469
+ /**
470
+ * Hide the payment loader.
471
+ */
472
+ @ReactMethod
473
+ fun hideLoader(promise: Promise) {
474
+ if (!checkInitialized(promise)) return
475
+
476
+ try {
477
+ promise.resolve(true)
478
+ } catch (e: Exception) {
479
+ promise.reject("HIDE_LOADER_ERROR", "Error hiding loader: ${e.message}", e)
480
+ }
481
+ }
482
+
483
+ /**
484
+ * Handle deep link
485
+ */
486
+ @ReactMethod
487
+ fun receiveDeeplink(url: String, promise: Promise) {
488
+ promise.resolve(true)
489
+ }
490
+
491
+ /**
492
+ * Start seamless payment lite flow.
493
+ */
494
+ @ReactMethod
495
+ fun startPaymentSeamlessLite(params: ReadableMap, language: String, promise: Promise) {
496
+ if (!checkInitialized(promise)) return
497
+ clearLastOTT() // 🧹 Clear any previous OTT tokens before starting new flow
498
+
499
+ try {
500
+ val checkoutSession = params.getString("checkoutSession") ?: run {
501
+ promise.reject("INVALID_PARAMS", "checkoutSession is required")
502
+ return
503
+ }
504
+
505
+ val countryCode = params.getString("countryCode") ?: run {
506
+ promise.reject("INVALID_PARAMS", "countryCode is required")
507
+ return
508
+ }
509
+
510
+ val showPaymentStatus = if (params.hasKey("showPaymentStatus")) {
511
+ params.getBoolean("showPaymentStatus")
512
+ } else {
513
+ true
514
+ }
515
+
516
+ val methodSelected = params.getMap("methodSelected")
517
+ val vaultedToken = methodSelected?.getString("vaultedToken")
518
+ val paymentMethodType = methodSelected?.getString("paymentMethodType") ?: run {
519
+ promise.reject("INVALID_PARAMS", "paymentMethodType is required")
520
+ return
521
+ }
522
+
523
+ val activity = currentActivity ?: run {
524
+ promise.reject("NO_ACTIVITY", "Current activity is not available")
525
+ return
526
+ }
527
+
528
+
529
+ // Update checkout session
530
+ activity.updateCheckoutSession(
531
+ checkoutSession = checkoutSession,
532
+ countryCode = countryCode
533
+ )
534
+
535
+ // Start seamless payment lite
536
+ activity.startPaymentSeamlessLite(
537
+ paymentSelected = PaymentSelected(
538
+ paymentMethodType = paymentMethodType,
539
+ vaultedToken = vaultedToken
540
+ ),
541
+ showPaymentStatus = showPaymentStatus,
542
+ callbackPaymentState = { paymentState ->
543
+ sendPaymentStatusEvent(paymentState)
544
+ promise.resolve(paymentState ?: "UNKNOWN")
545
+ }
546
+ )
547
+
548
+ } catch (e: Exception) {
549
+ promise.reject("SEAMLESS_ERROR", "Error starting seamless payment: ${e.message}", e)
550
+ }
551
+ }
552
+
553
+ @ReactMethod
554
+ fun addListener(eventName: String) {
555
+ // Required for RN built-in EventEmitter Calls
556
+ }
557
+
558
+ @ReactMethod
559
+ fun removeListeners(count: Int) {
560
+ // Required for RN built-in EventEmitter Calls
561
+ }
562
+
563
+ /**
564
+ * Get the last One Time Token that was generated.
565
+ * This is useful when the OTT event was emitted while React Native was paused.
566
+ *
567
+ * @param promise Promise that resolves with the last OTT token or null
568
+ */
569
+ @ReactMethod
570
+ fun getLastOneTimeToken(promise: Promise) {
571
+ try {
572
+ promise.resolve(lastOneTimeToken)
573
+ } catch (e: Exception) {
574
+ promise.reject("GET_OTT_ERROR", "Error getting last OTT: ${e.message}", e)
575
+ }
576
+ }
577
+
578
+ @ReactMethod
579
+ fun getLastOneTimeTokenInfo(promise: Promise) {
580
+ try {
581
+ val tokenInfo = lastOneTimeTokenInfo
582
+
583
+ if (tokenInfo != null) {
584
+ // Convert to JSON string first
585
+ val jsonString = gson.toJson(tokenInfo)
586
+
587
+ // Use existing conversion method
588
+ val params = convertJsonToMap(jsonString)
589
+
590
+ promise.resolve(params)
591
+ } else {
592
+ promise.resolve(null)
593
+ }
594
+ } catch (e: Exception) {
595
+ promise.reject("GET_LAST_TOKEN_INFO_ERROR", e.message, e)
596
+ }
597
+ }
598
+
599
+ /**
600
+ * Clear the last OTT tokens manually from JavaScript.
601
+ * This is useful to ensure clean state before starting a new payment flow.
602
+ *
603
+ * @param promise Promise that resolves to true when cleared
604
+ */
605
+ @ReactMethod
606
+ fun clearLastOneTimeToken(promise: Promise) {
607
+ try {
608
+ clearLastOTT()
609
+ promise.resolve(true)
610
+ } catch (e: Exception) {
611
+ promise.reject("CLEAR_OTT_ERROR", "Error clearing last OTT: ${e.message}", e)
612
+ }
613
+ }
614
+
615
+ // Lifecycle Methods
616
+ override fun onHostResume() {}
617
+ override fun onHostPause() {}
618
+ override fun onHostDestroy() {}
619
+
620
+ // Helper Methods
621
+ private fun checkInitialized(promise: Promise): Boolean {
622
+
623
+ // Check if SDK was initialized by Activity
624
+ if (sdkInitializedByActivity && !isInitialized) {
625
+ isInitialized = true
626
+ }
627
+
628
+ if (!isInitialized && !sdkInitializedByActivity) {
629
+ promise.reject("NOT_INITIALIZED", "Yuno SDK is not initialized. Call initialize() first.")
630
+ return false
631
+ }
632
+
633
+ return true
634
+ }
635
+
636
+ private fun sendEvent(eventName: String, params: Any?) {
637
+ try {
638
+ reactContext
639
+ .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
640
+ .emit(eventName, params)
641
+ } catch (e: Exception) {
642
+ }
643
+ }
644
+
645
+ internal fun sendOneTimeTokenEvent(token: String?) {
646
+ token?.let {
647
+ sendEvent("YunoOneTimeToken", it)
648
+ } ?: run {
649
+ }
650
+ }
651
+
652
+ internal fun sendPaymentStatusEvent(status: String?) {
653
+ val convertedStatus = convertPaymentStatus(status)
654
+ val params = Arguments.createMap().apply {
655
+ putString("status", convertedStatus)
656
+ }
657
+ sendEvent("YunoPaymentStatus", params)
658
+ }
659
+
660
+ internal fun sendEnrollmentStatusEvent(status: String?) {
661
+ val convertedStatus = convertEnrollmentStatus(status)
662
+ val params = Arguments.createMap().apply {
663
+ putString("status", convertedStatus)
664
+ }
665
+ sendEvent("YunoEnrollmentStatus", params)
666
+ }
667
+
668
+ private fun convertPaymentStatus(status: String?): String {
669
+ return when (status) {
670
+ PAYMENT_STATE_SUCCEEDED -> "SUCCEEDED"
671
+ PAYMENT_STATE_FAIL -> "FAILED"
672
+ PAYMENT_STATE_REJECT -> "REJECTED"
673
+ PAYMENT_STATE_STATE_CANCELED_BY_USER -> "CANCELLED"
674
+ PAYMENT_STATE_PROCESSING -> "PENDING"
675
+ PAYMENT_STATE_INTERNAL_ERROR -> "ERROR"
676
+ else -> "UNKNOWN"
677
+ }
678
+ }
679
+
680
+ private fun convertEnrollmentStatus(status: String?): String {
681
+ return when (status) {
682
+ ENROLLMENT_STATE_SUCCEEDED -> "SUCCEEDED"
683
+ ENROLLMENT_STATE_FAIL -> "FAILED"
684
+ ENROLLMENT_STATE_REJECT -> "REJECTED"
685
+ ENROLLMENT_STATE_CANCELED_BY_USER -> "CANCELLED"
686
+ ENROLLMENT_STATE_PROCESSING -> "PENDING"
687
+ ENROLLMENT_STATE_INTERNAL_ERROR -> "ERROR"
688
+ else -> "UNKNOWN"
689
+ }
690
+ }
691
+
692
+ /**
693
+ * Sends OneTimeTokenInfo event with full information including card and customer data.
694
+ * Uses Gson to convert the tokenModel to JSON and then to WritableMap.
695
+ */
696
+ internal fun sendOneTimeTokenInfoEvent(tokenModel: OneTimeTokenModel?) {
697
+ tokenModel?.let {
698
+ try {
699
+
700
+ // Store the token info in companion object for later retrieval
701
+ lastOneTimeTokenInfo = it
702
+
703
+ // Convert tokenModel to JSON using Gson
704
+ val gson = Gson()
705
+ val jsonString = gson.toJson(it)
706
+
707
+ // Convert JSON to WritableMap
708
+ val tokenMap = convertJsonToMap(jsonString)
709
+ sendEvent("YunoOneTimeTokenInfo", tokenMap)
710
+ } catch (e: Exception) {
711
+ }
712
+ } ?: run {
713
+ }
714
+ }
715
+
716
+ /**
717
+ * Converts a JSON string to WritableMap for React Native.
718
+ */
719
+ private fun convertJsonToMap(jsonString: String): WritableMap {
720
+ val map = Arguments.createMap()
721
+ try {
722
+ val jsonObject = org.json.JSONObject(jsonString)
723
+ val keys = jsonObject.keys()
724
+ while (keys.hasNext()) {
725
+ val key = keys.next()
726
+ val value = jsonObject.get(key)
727
+ when (value) {
728
+ is String -> map.putString(key, value)
729
+ is Int -> map.putInt(key, value)
730
+ is Double -> map.putDouble(key, value)
731
+ is Boolean -> map.putBoolean(key, value)
732
+ is org.json.JSONObject -> map.putMap(key, convertJsonObjectToMap(value))
733
+ is org.json.JSONArray -> map.putArray(key, convertJsonArrayToArray(value))
734
+ org.json.JSONObject.NULL -> map.putNull(key)
735
+ }
736
+ }
737
+ } catch (e: Exception) {
738
+ }
739
+ return map
740
+ }
741
+
742
+ private fun convertJsonObjectToMap(jsonObject: org.json.JSONObject): WritableMap {
743
+ val map = Arguments.createMap()
744
+ val keys = jsonObject.keys()
745
+ while (keys.hasNext()) {
746
+ val key = keys.next()
747
+ val value = jsonObject.get(key)
748
+ when (value) {
749
+ is String -> map.putString(key, value)
750
+ is Int -> map.putInt(key, value)
751
+ is Double -> map.putDouble(key, value)
752
+ is Boolean -> map.putBoolean(key, value)
753
+ is org.json.JSONObject -> map.putMap(key, convertJsonObjectToMap(value))
754
+ is org.json.JSONArray -> map.putArray(key, convertJsonArrayToArray(value))
755
+ org.json.JSONObject.NULL -> map.putNull(key)
756
+ }
757
+ }
758
+ return map
759
+ }
760
+
761
+ private fun convertJsonArrayToArray(jsonArray: org.json.JSONArray): WritableArray {
762
+ val array = Arguments.createArray()
763
+ for (i in 0 until jsonArray.length()) {
764
+ val value = jsonArray.get(i)
765
+ when (value) {
766
+ is String -> array.pushString(value)
767
+ is Int -> array.pushInt(value)
768
+ is Double -> array.pushDouble(value)
769
+ is Boolean -> array.pushBoolean(value)
770
+ is org.json.JSONObject -> array.pushMap(convertJsonObjectToMap(value))
771
+ is org.json.JSONArray -> array.pushArray(convertJsonArrayToArray(value))
772
+ org.json.JSONObject.NULL -> array.pushNull()
773
+ }
774
+ }
775
+ return array
776
+ }
777
+ }