@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.
- package/LICENSE +22 -0
- package/README.md +621 -0
- package/android/build.gradle +131 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +4 -0
- package/android/src/main/java/com/yunosdkreactnative/YunoPaymentMethodsViewManager.kt +194 -0
- package/android/src/main/java/com/yunosdkreactnative/YunoSdkModule.kt +777 -0
- package/android/src/main/java/com/yunosdkreactnative/YunoSdkPackage.kt +24 -0
- package/ios/YunoSdk.m +65 -0
- package/ios/YunoSdk.podspec +48 -0
- package/ios/YunoSdk.swift +442 -0
- package/lib/commonjs/YunoPaymentMethods.js +145 -0
- package/lib/commonjs/YunoPaymentMethods.js.map +1 -0
- package/lib/commonjs/YunoSdk.js +455 -0
- package/lib/commonjs/YunoSdk.js.map +1 -0
- package/lib/commonjs/core/enums/CardFlow.js +26 -0
- package/lib/commonjs/core/enums/CardFlow.js.map +1 -0
- package/lib/commonjs/core/enums/YunoLanguage.js +58 -0
- package/lib/commonjs/core/enums/YunoLanguage.js.map +1 -0
- package/lib/commonjs/core/enums/YunoStatus.js +40 -0
- package/lib/commonjs/core/enums/YunoStatus.js.map +1 -0
- package/lib/commonjs/core/enums/index.js +27 -0
- package/lib/commonjs/core/enums/index.js.map +1 -0
- package/lib/commonjs/core/index.js +28 -0
- package/lib/commonjs/core/index.js.map +1 -0
- package/lib/commonjs/core/types/AndroidConfig.js +2 -0
- package/lib/commonjs/core/types/AndroidConfig.js.map +1 -0
- package/lib/commonjs/core/types/EnrollmentArguments.js +2 -0
- package/lib/commonjs/core/types/EnrollmentArguments.js.map +1 -0
- package/lib/commonjs/core/types/IosConfig.js +2 -0
- package/lib/commonjs/core/types/IosConfig.js.map +1 -0
- package/lib/commonjs/core/types/OneTimeTokenInfo.js +2 -0
- package/lib/commonjs/core/types/OneTimeTokenInfo.js.map +1 -0
- package/lib/commonjs/core/types/SeamlessArguments.js +6 -0
- package/lib/commonjs/core/types/SeamlessArguments.js.map +1 -0
- package/lib/commonjs/core/types/StartPayment.js +2 -0
- package/lib/commonjs/core/types/StartPayment.js.map +1 -0
- package/lib/commonjs/core/types/YunoConfig.js +6 -0
- package/lib/commonjs/core/types/YunoConfig.js.map +1 -0
- package/lib/commonjs/core/types/index.js +2 -0
- package/lib/commonjs/core/types/index.js.map +1 -0
- package/lib/commonjs/index.js +36 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/module/YunoPaymentMethods.js +138 -0
- package/lib/module/YunoPaymentMethods.js.map +1 -0
- package/lib/module/YunoSdk.js +448 -0
- package/lib/module/YunoSdk.js.map +1 -0
- package/lib/module/core/enums/CardFlow.js +20 -0
- package/lib/module/core/enums/CardFlow.js.map +1 -0
- package/lib/module/core/enums/YunoLanguage.js +52 -0
- package/lib/module/core/enums/YunoLanguage.js.map +1 -0
- package/lib/module/core/enums/YunoStatus.js +34 -0
- package/lib/module/core/enums/YunoStatus.js.map +1 -0
- package/lib/module/core/enums/index.js +4 -0
- package/lib/module/core/enums/index.js.map +1 -0
- package/lib/module/core/index.js +3 -0
- package/lib/module/core/index.js.map +1 -0
- package/lib/module/core/types/AndroidConfig.js +2 -0
- package/lib/module/core/types/AndroidConfig.js.map +1 -0
- package/lib/module/core/types/EnrollmentArguments.js +2 -0
- package/lib/module/core/types/EnrollmentArguments.js.map +1 -0
- package/lib/module/core/types/IosConfig.js +2 -0
- package/lib/module/core/types/IosConfig.js.map +1 -0
- package/lib/module/core/types/OneTimeTokenInfo.js +2 -0
- package/lib/module/core/types/OneTimeTokenInfo.js.map +1 -0
- package/lib/module/core/types/SeamlessArguments.js +2 -0
- package/lib/module/core/types/SeamlessArguments.js.map +1 -0
- package/lib/module/core/types/StartPayment.js +2 -0
- package/lib/module/core/types/StartPayment.js.map +1 -0
- package/lib/module/core/types/YunoConfig.js +2 -0
- package/lib/module/core/types/YunoConfig.js.map +1 -0
- package/lib/module/core/types/index.js +2 -0
- package/lib/module/core/types/index.js.map +1 -0
- package/lib/module/index.js +4 -0
- package/lib/module/index.js.map +1 -0
- package/package.json +142 -0
- package/src/YunoPaymentMethods.tsx +196 -0
- package/src/YunoSdk.ts +518 -0
- package/src/core/enums/CardFlow.ts +18 -0
- package/src/core/enums/YunoLanguage.ts +50 -0
- package/src/core/enums/YunoStatus.ts +32 -0
- package/src/core/enums/index.ts +3 -0
- package/src/core/index.ts +2 -0
- package/src/core/types/AndroidConfig.ts +17 -0
- package/src/core/types/EnrollmentArguments.ts +32 -0
- package/src/core/types/IosConfig.ts +17 -0
- package/src/core/types/OneTimeTokenInfo.ts +207 -0
- package/src/core/types/SeamlessArguments.ts +42 -0
- package/src/core/types/StartPayment.ts +59 -0
- package/src/core/types/YunoConfig.ts +55 -0
- package/src/core/types/index.ts +7 -0
- 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
|
+
}
|