nimbbl-mobile-react-native-sdk 1.2.0 → 1.3.0

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.
@@ -3,19 +3,97 @@ package com.nimbbl.reactnative
3
3
  import android.app.Activity
4
4
  import android.content.Intent
5
5
  import com.facebook.react.bridge.*
6
- import com.facebook.react.modules.core.DeviceEventManagerModule
7
6
  import tech.nimbbl.webviewsdk.core.NimbblCheckoutSDK
8
7
  import tech.nimbbl.webviewsdk.core.NimbblShopOrderCreation
9
8
  import tech.nimbbl.webviewsdk.models.NimbblCheckoutOptions
10
9
  import tech.nimbbl.webviewsdk.models.interfaces.NimbblCheckoutPaymentListener
11
10
  import kotlinx.coroutines.*
12
11
  import retrofit2.Response
12
+ import java.io.Serializable
13
+ import org.json.JSONObject
14
+ import org.json.JSONException
13
15
 
14
16
  class NimbblReactNativeSDKModule(private val reactContext: ReactApplicationContext) :
15
17
  ReactContextBaseJavaModule(reactContext), ActivityEventListener {
16
18
 
17
19
  companion object {
18
- private const val TAG = "NimbblReactNativeSDK"
20
+ private var instance: NimbblReactNativeSDKModule? = null
21
+
22
+ fun handleCheckoutResponse(data: MutableMap<String, Any>) {
23
+
24
+ try {
25
+ instance?.let { module ->
26
+ // Convert Map to WritableMap
27
+ val responseData = Arguments.createMap().apply {
28
+ data.forEach { (key, value) ->
29
+ when (value) {
30
+ is String -> {
31
+ // Special handling for 'order' field - parse JSON string to object
32
+ if (key == "order" && value.startsWith("{") && value.endsWith("}")) {
33
+ try {
34
+ val jsonObject = JSONObject(value)
35
+ val orderMap = Arguments.createMap()
36
+
37
+ // Convert JSONObject to WritableMap
38
+ jsonObject.keys().forEach { jsonKey ->
39
+ val jsonValue = jsonObject.get(jsonKey)
40
+ when (jsonValue) {
41
+ is String -> orderMap.putString(jsonKey, jsonValue)
42
+ is Number -> orderMap.putDouble(jsonKey, jsonValue.toDouble())
43
+ is Boolean -> orderMap.putBoolean(jsonKey, jsonValue)
44
+ is JSONObject -> {
45
+ val nestedMap = Arguments.createMap()
46
+ jsonValue.keys().forEach { nestedKey ->
47
+ val nestedValue = jsonValue.get(nestedKey)
48
+ when (nestedValue) {
49
+ is String -> nestedMap.putString(nestedKey, nestedValue)
50
+ is Number -> nestedMap.putDouble(nestedKey, nestedValue.toDouble())
51
+ is Boolean -> nestedMap.putBoolean(nestedKey, nestedValue)
52
+ else -> nestedMap.putString(nestedKey, nestedValue.toString())
53
+ }
54
+ }
55
+ orderMap.putMap(jsonKey, nestedMap)
56
+ }
57
+ else -> orderMap.putString(jsonKey, jsonValue.toString())
58
+ }
59
+ }
60
+
61
+ putMap(key, orderMap)
62
+ } catch (e: JSONException) {
63
+ putString(key, value)
64
+ }
65
+ } else {
66
+ putString(key, value)
67
+ }
68
+ }
69
+ is Number -> putDouble(key, value.toDouble())
70
+ is Boolean -> putBoolean(key, value)
71
+ is Map<*, *> -> {
72
+ val nestedMap = Arguments.createMap()
73
+ (value as Map<String, Any>).forEach { (nestedKey, nestedValue) ->
74
+ when (nestedValue) {
75
+ is String -> nestedMap.putString(nestedKey, nestedValue)
76
+ is Number -> nestedMap.putDouble(nestedKey, nestedValue.toDouble())
77
+ is Boolean -> nestedMap.putBoolean(nestedKey, nestedValue)
78
+ else -> nestedMap.putString(nestedKey, nestedValue.toString())
79
+ }
80
+ }
81
+ putMap(key, nestedMap)
82
+ }
83
+ else -> putString(key, value.toString())
84
+ }
85
+ }
86
+ }
87
+
88
+ // Use callback if available
89
+ if (module.checkoutCallback != null) {
90
+ module.checkoutCallback?.invoke(responseData)
91
+ }
92
+ }
93
+ } catch (e: Exception) {
94
+ // Error in handleCheckoutResponse - ignore
95
+ }
96
+ }
19
97
  }
20
98
 
21
99
  private var config: MutableMap<String, Any> = mutableMapOf()
@@ -24,28 +102,24 @@ class NimbblReactNativeSDKModule(private val reactContext: ReactApplicationConte
24
102
 
25
103
  init {
26
104
  reactContext.addActivityEventListener(this)
105
+ instance = this
27
106
  }
28
107
 
29
108
  override fun getName(): String {
30
109
  return "NimbblReactNativeSDK"
31
110
  }
32
111
 
33
- override fun getConstants(): MutableMap<String, Any> {
34
- return mutableMapOf(
35
- "SDK_VERSION" to "1.0.0",
36
- "PLATFORM" to "android"
37
- )
38
- }
39
-
112
+
40
113
  @ReactMethod
41
114
  fun addListener(@Suppress("UNUSED_PARAMETER") eventName: String) {
42
115
  // Required for NativeEventEmitter - no action needed
43
116
  }
44
-
117
+
45
118
  @ReactMethod
46
119
  fun removeListeners(@Suppress("UNUSED_PARAMETER") count: Int) {
47
120
  // Required for NativeEventEmitter - no action needed
48
121
  }
122
+
49
123
 
50
124
  @ReactMethod
51
125
  fun setCheckoutCallback(callback: Callback) {
@@ -53,7 +127,7 @@ class NimbblReactNativeSDKModule(private val reactContext: ReactApplicationConte
53
127
  try {
54
128
  callback.invoke(result)
55
129
  } catch (e: Exception) {
56
- // Error calling checkout callback, ignore
130
+ // Error calling React Native callback - ignore
57
131
  }
58
132
  }
59
133
  }
@@ -68,19 +142,14 @@ class NimbblReactNativeSDKModule(private val reactContext: ReactApplicationConte
68
142
 
69
143
  // Extract configuration
70
144
  this.config.clear()
71
- this.config["environment"] = config.getString("environment") ?: "production"
72
-
73
- config.getMap("options")?.let { options ->
74
- this.config["timeout"] = if (options.hasKey("timeout")) options.getInt("timeout") else 30000
75
- this.config["enable_logging"] = if (options.hasKey("enable_logging")) options.getBoolean("enable_logging") else false
76
- this.config["enable_analytics"] = if (options.hasKey("enable_analytics")) options.getBoolean("enable_analytics") else true
77
- this.config["api_base_url"] = if (options.hasKey("api_base_url")) options.getString("api_base_url") ?: "https://api.nimbbl.tech" else "https://api.nimbbl.tech"
78
- }
145
+ this.config["api_base_url"] = config.getString("api_base_url") ?: "https://api.nimbbl.tech"
79
146
 
80
147
  // Set environment URL on the native SDK during initialization
81
148
  val apiBaseUrl = this.config["api_base_url"] as? String
82
149
  if (!apiBaseUrl.isNullOrEmpty()) {
83
- NimbblCheckoutSDK.getInstance().setEnvironmentUrl(apiBaseUrl)
150
+ // Ensure URL ends with / to prevent URL construction issues
151
+ val normalizedUrl = if (apiBaseUrl.endsWith("/")) apiBaseUrl else apiBaseUrl + "/"
152
+ NimbblCheckoutSDK.getInstance().setEnvironmentUrl(normalizedUrl)
84
153
  }
85
154
 
86
155
  isInitialized = true
@@ -243,21 +312,96 @@ class NimbblReactNativeSDKModule(private val reactContext: ReactApplicationConte
243
312
  return
244
313
  }
245
314
 
246
- // Start the custom NimbblCheckoutActivity instead of trying to use MainActivity
315
+ // Use the NimbblCheckoutActivity with proper callback setup
247
316
  try {
248
-
249
- // Use startActivityForResult to properly handle callbacks
250
- val intent = Intent(currentActivity, NimbblCheckoutActivity::class.java).apply {
251
- putExtra(NimbblCheckoutActivity.EXTRA_ORDER_TOKEN, orderToken)
252
- putExtra(NimbblCheckoutActivity.EXTRA_PAYMENT_MODE_CODE, paymentModeCode)
253
- putExtra(NimbblCheckoutActivity.EXTRA_BANK_CODE, bankCode)
254
- putExtra(NimbblCheckoutActivity.EXTRA_WALLET_CODE, walletCode)
255
- putExtra(NimbblCheckoutActivity.EXTRA_PAYMENT_FLOW, paymentFlow)
317
+ // Set up the callback to receive the result
318
+ NimbblCheckoutActivity.setResultCallback { result ->
319
+
320
+ // Convert Map to WritableMap
321
+ val responseData = Arguments.createMap().apply {
322
+ result.forEach { (key, value) ->
323
+ when (value) {
324
+ is String -> {
325
+ // Special handling for 'order' field - parse JSON string to object
326
+ if (key == "order" && value.startsWith("{") && value.endsWith("}")) {
327
+ try {
328
+ val jsonObject = JSONObject(value)
329
+ val orderMap = Arguments.createMap()
330
+
331
+ // Convert JSONObject to WritableMap
332
+ jsonObject.keys().forEach { jsonKey ->
333
+ val jsonValue = jsonObject.get(jsonKey)
334
+ when (jsonValue) {
335
+ is String -> orderMap.putString(jsonKey, jsonValue)
336
+ is Number -> orderMap.putDouble(jsonKey, jsonValue.toDouble())
337
+ is Boolean -> orderMap.putBoolean(jsonKey, jsonValue)
338
+ is JSONObject -> {
339
+ val nestedMap = Arguments.createMap()
340
+ jsonValue.keys().forEach { nestedKey ->
341
+ val nestedValue = jsonValue.get(nestedKey)
342
+ when (nestedValue) {
343
+ is String -> nestedMap.putString(nestedKey, nestedValue)
344
+ is Number -> nestedMap.putDouble(nestedKey, nestedValue.toDouble())
345
+ is Boolean -> nestedMap.putBoolean(nestedKey, nestedValue)
346
+ else -> nestedMap.putString(nestedKey, nestedValue.toString())
347
+ }
348
+ }
349
+ orderMap.putMap(jsonKey, nestedMap)
350
+ }
351
+ else -> orderMap.putString(jsonKey, jsonValue.toString())
352
+ }
353
+ }
354
+
355
+ putMap(key, orderMap)
356
+ } catch (e: JSONException) {
357
+ putString(key, value)
358
+ }
359
+ } else {
360
+ putString(key, value)
361
+ }
362
+ }
363
+ is Number -> putDouble(key, value.toDouble())
364
+ is Boolean -> putBoolean(key, value)
365
+ is Map<*, *> -> {
366
+ val nestedMap = Arguments.createMap()
367
+ (value as Map<String, Any>).forEach { (nestedKey, nestedValue) ->
368
+ when (nestedValue) {
369
+ is String -> nestedMap.putString(nestedKey, nestedValue)
370
+ is Number -> nestedMap.putDouble(nestedKey, nestedValue.toDouble())
371
+ is Boolean -> nestedMap.putBoolean(nestedKey, nestedValue)
372
+ else -> nestedMap.putString(nestedKey, nestedValue.toString())
373
+ }
374
+ }
375
+ putMap(key, nestedMap)
376
+ }
377
+ else -> putString(key, value.toString())
378
+ }
379
+ }
380
+ }
381
+
382
+ // Use callback if available
383
+ if (checkoutCallback != null) {
384
+ try {
385
+ checkoutCallback?.invoke(responseData)
386
+ } catch (e: Exception) {
387
+ // Error invoking checkout callback - ignore
388
+ }
389
+ }
256
390
  }
257
391
 
258
- // Use a request code to identify this activity result
259
- val REQUEST_CHECKOUT = 1001
260
- currentActivity.startActivityForResult(intent, REQUEST_CHECKOUT)
392
+ // Add a small delay to ensure callback is properly set up
393
+ android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
394
+ // Start the NimbblCheckoutActivity
395
+ val intent = Intent(currentActivity, NimbblCheckoutActivity::class.java).apply {
396
+ putExtra(NimbblCheckoutActivity.EXTRA_ORDER_TOKEN, orderToken)
397
+ putExtra(NimbblCheckoutActivity.EXTRA_PAYMENT_MODE_CODE, paymentModeCode)
398
+ putExtra(NimbblCheckoutActivity.EXTRA_BANK_CODE, bankCode)
399
+ putExtra(NimbblCheckoutActivity.EXTRA_WALLET_CODE, walletCode)
400
+ putExtra(NimbblCheckoutActivity.EXTRA_PAYMENT_FLOW, paymentFlow)
401
+ }
402
+
403
+ currentActivity.startActivity(intent)
404
+ }, 50) // 50ms delay to ensure callback is set up
261
405
 
262
406
  } catch (e: Exception) {
263
407
  promise.reject("CHECKOUT_FAILED", "Error starting checkout activity: ${e.message}")
@@ -276,47 +420,98 @@ class NimbblReactNativeSDKModule(private val reactContext: ReactApplicationConte
276
420
  }
277
421
 
278
422
 
279
- private fun sendEvent(eventName: String, params: WritableMap?) {
280
- try {
281
- reactContext
282
- .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
283
- .emit(eventName, params)
284
- } catch (e: Exception) {
285
- // Error sending event, ignore
286
- }
287
- }
288
423
 
289
424
  override fun onActivityResult(activity: Activity?, requestCode: Int, resultCode: Int, data: Intent?) {
425
+
290
426
  // Handle NimbblCheckoutActivity results
291
427
  if (requestCode == 1001) {
292
428
  val responseData = if (data != null) {
293
- val status = data.getStringExtra("status")
294
-
295
- Arguments.createMap().apply {
296
- putString("status", status)
297
- putString("order_id", data.getStringExtra("order_id") ?: "")
298
- putString("payment_id", data.getStringExtra("payment_id") ?: "")
299
- putString("data", data.getStringExtra("response") ?: "")
429
+ val rawData = data.getSerializableExtra("response_data") as? MutableMap<String, Any>
430
+
431
+ if (rawData != null) {
432
+ // Convert Map to WritableMap directly
433
+ Arguments.createMap().apply {
434
+ rawData.forEach { (key, value) ->
435
+ when (value) {
436
+ is String -> {
437
+ // Special handling for 'order' field - parse JSON string to object
438
+ if (key == "order" && value.startsWith("{") && value.endsWith("}")) {
439
+ try {
440
+ val jsonObject = JSONObject(value)
441
+ val orderMap = Arguments.createMap()
442
+
443
+ // Convert JSONObject to WritableMap
444
+ jsonObject.keys().forEach { jsonKey ->
445
+ val jsonValue = jsonObject.get(jsonKey)
446
+ when (jsonValue) {
447
+ is String -> orderMap.putString(jsonKey, jsonValue)
448
+ is Number -> orderMap.putDouble(jsonKey, jsonValue.toDouble())
449
+ is Boolean -> orderMap.putBoolean(jsonKey, jsonValue)
450
+ is JSONObject -> {
451
+ val nestedMap = Arguments.createMap()
452
+ jsonValue.keys().forEach { nestedKey ->
453
+ val nestedValue = jsonValue.get(nestedKey)
454
+ when (nestedValue) {
455
+ is String -> nestedMap.putString(nestedKey, nestedValue)
456
+ is Number -> nestedMap.putDouble(nestedKey, nestedValue.toDouble())
457
+ is Boolean -> nestedMap.putBoolean(nestedKey, nestedValue)
458
+ else -> nestedMap.putString(nestedKey, nestedValue.toString())
459
+ }
460
+ }
461
+ orderMap.putMap(jsonKey, nestedMap)
462
+ }
463
+ else -> orderMap.putString(jsonKey, jsonValue.toString())
464
+ }
465
+ }
466
+
467
+ putMap(key, orderMap)
468
+ } catch (e: JSONException) {
469
+ putString(key, value)
470
+ }
471
+ } else {
472
+ putString(key, value)
473
+ }
474
+ }
475
+ is Number -> putDouble(key, value.toDouble())
476
+ is Boolean -> putBoolean(key, value)
477
+ is Map<*, *> -> {
478
+ val nestedMap = Arguments.createMap()
479
+ (value as Map<String, Any>).forEach { (nestedKey, nestedValue) ->
480
+ when (nestedValue) {
481
+ is String -> nestedMap.putString(nestedKey, nestedValue)
482
+ is Number -> nestedMap.putDouble(nestedKey, nestedValue.toDouble())
483
+ is Boolean -> nestedMap.putBoolean(nestedKey, nestedValue)
484
+ else -> nestedMap.putString(nestedKey, nestedValue.toString())
485
+ }
486
+ }
487
+ putMap(key, nestedMap)
488
+ }
489
+ else -> putString(key, value.toString())
490
+ }
491
+ }
492
+ }
493
+ } else {
494
+ Arguments.createMap().apply {
495
+ putString("status", "failed")
496
+ putString("message", "No response data received")
497
+ }
300
498
  }
301
499
  } else {
302
500
  Arguments.createMap().apply {
303
501
  putString("status", "failed")
304
502
  putString("message", "Payment was cancelled or failed")
305
- putString("order_id", "")
306
- putString("payment_id", "")
307
- putString("data", "")
308
503
  }
309
504
  }
310
505
 
311
- // Use callback if available, otherwise fall back to event
506
+ // Use callback if available
312
507
  if (checkoutCallback != null) {
313
508
  checkoutCallback?.invoke(responseData)
314
- } else {
315
- sendEvent("checkout_response", responseData)
316
509
  }
510
+ // Note: Removed fallback to event emitter for cleaner implementation
317
511
  }
318
512
  }
319
513
 
514
+
320
515
  override fun onNewIntent(intent: Intent?) {
321
516
  // Handle new intents if needed
322
517
  }
@@ -325,5 +520,5 @@ class NimbblReactNativeSDKModule(private val reactContext: ReactApplicationConte
325
520
  super.onCatalystInstanceDestroy()
326
521
  reactContext.removeActivityEventListener(this)
327
522
  }
328
-
523
+
329
524
  }
@@ -8,7 +8,6 @@ class NimbblReactNativeSDK: RCTEventEmitter, NimbblCheckoutSDKDelegate {
8
8
 
9
9
  private var config: [String: Any] = [:]
10
10
  private var isInitialized = false
11
- private var hasListeners = false
12
11
  private var checkoutCallback: RCTResponseSenderBlock?
13
12
 
14
13
  override init() {
@@ -16,30 +15,11 @@ class NimbblReactNativeSDK: RCTEventEmitter, NimbblCheckoutSDKDelegate {
16
15
  NimbblCheckoutSDK.shared.delegate = self
17
16
  }
18
17
 
19
- override func supportedEvents() -> [String]! {
20
- return [
21
- "checkout_response"
22
- ]
23
- }
24
-
25
- override func constantsToExport() -> [AnyHashable: Any]! {
26
- return [
27
- "SDK_VERSION": "1.0.0",
28
- "PLATFORM": "ios"
29
- ]
30
- }
31
18
 
32
19
  override static func requiresMainQueueSetup() -> Bool {
33
20
  return false
34
21
  }
35
22
 
36
- override func startObserving() {
37
- hasListeners = true
38
- }
39
-
40
- override func stopObserving() {
41
- hasListeners = false
42
- }
43
23
 
44
24
  @objc
45
25
  override func addListener(_ eventName: String!) {
@@ -62,19 +42,14 @@ class NimbblReactNativeSDK: RCTEventEmitter, NimbblCheckoutSDKDelegate {
62
42
 
63
43
  // Extract configuration
64
44
  self.config.removeAll()
65
- self.config["environment"] = config["environment"] as? String ?? "production"
66
-
67
- if let options = config["options"] as? [String: Any] {
68
- self.config["timeout"] = options["timeout"] as? Int ?? 30000
69
- self.config["enable_logging"] = options["enable_logging"] as? Bool ?? false
70
- self.config["enable_analytics"] = options["enable_analytics"] as? Bool ?? true
71
- self.config["api_base_url"] = options["api_base_url"] as? String ?? "https://api.nimbbl.tech"
72
- }
45
+ self.config["api_base_url"] = config["api_base_url"] as? String ?? "https://api.nimbbl.tech"
73
46
 
74
47
  // Set environment URL on the native SDK during initialization
75
48
  let apiBaseUrl = self.config["api_base_url"] as? String
76
49
  if let apiBaseUrl = apiBaseUrl {
77
- NimbblCheckoutSDK.shared.environmentUrl = apiBaseUrl
50
+ // Ensure URL ends with / to prevent URL construction issues
51
+ let normalizedUrl = apiBaseUrl.hasSuffix("/") ? apiBaseUrl : apiBaseUrl + "/"
52
+ NimbblCheckoutSDK.shared.environmentUrl = normalizedUrl
78
53
  }
79
54
 
80
55
  isInitialized = true
@@ -199,11 +174,9 @@ class NimbblReactNativeSDK: RCTEventEmitter, NimbblCheckoutSDKDelegate {
199
174
 
200
175
  func onCheckoutResponse(data: [AnyHashable: Any]) {
201
176
  if let callback = checkoutCallback {
202
- // Use direct callback if available
177
+ // Use direct callback
203
178
  callback([data])
204
- } else if hasListeners {
205
- // Fallback to event emitter
206
- sendEvent(withName: "checkout_response", body: data)
207
179
  }
180
+ // Note: Removed fallback to event emitter for cleaner implementation
208
181
  }
209
182
  }
@@ -1,11 +1,11 @@
1
1
  /**
2
2
  * @fileoverview Main Nimbbl SDK for React Native
3
- * @version 1.2.0
3
+ * @version 1.3.0
4
4
  * @author Nimbbl Tech
5
5
  *
6
6
  * This is a wrapper around the native Android and iOS SDKs
7
7
  */
8
- import { SDKConfig } from './types';
8
+ import { SDKConfig, CheckoutOptions } from './types';
9
9
  /**
10
10
  * Main Nimbbl SDK Class
11
11
  * Provides the primary interface for integrating Nimbbl payment functionality
@@ -15,8 +15,6 @@ export default class NimbblSDK {
15
15
  private static shared;
16
16
  private config;
17
17
  private isInitialized;
18
- private eventEmitter;
19
- private eventListeners;
20
18
  constructor();
21
19
  /**
22
20
  * Get shared instance (matching iOS pattern)
@@ -53,33 +51,7 @@ export default class NimbblSDK {
53
51
  * @param options - Checkout options
54
52
  * @returns Promise resolving to checkout result
55
53
  */
56
- checkout(options: {
57
- orderToken: string;
58
- paymentModeCode?: string;
59
- bankCode?: string;
60
- walletCode?: string;
61
- paymentFlow?: string;
62
- }): Promise<{
63
- success: boolean;
64
- message?: string;
65
- data?: any;
66
- }>;
67
- /**
68
- * Add unified checkout response listener (recommended approach)
69
- * This method handles both success and failure cases in a single callback
70
- * @param listener - Callback function that receives checkout response data
71
- */
72
- addCheckoutResponseListener(listener: (data: any) => void): void;
73
- /**
74
- * Remove unified checkout response listener
75
- * @param listener - Callback function to remove
76
- */
77
- removeCheckoutResponseListener(listener: (data: any) => void): void;
78
- /**
79
- * Validate SDK configuration
80
- * @param config - Configuration to validate
81
- */
82
- private validateConfig;
54
+ checkout(options: CheckoutOptions): Promise<any>;
83
55
  }
84
56
  export declare const nimbblSDK: NimbblSDK;
85
57
  //# sourceMappingURL=NimbblSDK.d.ts.map