@stripe/stripe-react-native 0.57.1 → 0.57.3

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 (115) hide show
  1. package/README.md +0 -8
  2. package/android/build.gradle +1 -0
  3. package/android/src/main/java/com/reactnativestripesdk/EmbeddedPaymentElementView.kt +49 -0
  4. package/android/src/main/java/com/reactnativestripesdk/EmbeddedPaymentElementViewManager.kt +61 -0
  5. package/android/src/main/java/com/reactnativestripesdk/EventEmitterCompat.kt +4 -0
  6. package/android/src/main/java/com/reactnativestripesdk/PaymentSheetManager.kt +131 -30
  7. package/android/src/main/java/com/reactnativestripesdk/customersheet/CustomerSheetManager.kt +38 -13
  8. package/android/src/oldarch/java/com/facebook/react/viewmanagers/EmbeddedPaymentElementViewManagerDelegate.java +3 -0
  9. package/android/src/oldarch/java/com/facebook/react/viewmanagers/EmbeddedPaymentElementViewManagerInterface.java +2 -0
  10. package/android/src/test/java/com/reactnativestripesdk/DrawableConversionPropertyTest.kt +224 -0
  11. package/android/src/test/java/com/reactnativestripesdk/DrawableConversionTest.kt +146 -0
  12. package/android/src/test/java/com/reactnativestripesdk/DrawableLoadingTest.kt +150 -0
  13. package/android/src/test/java/com/reactnativestripesdk/PaymentOptionImageConsistencyTest.kt +186 -0
  14. package/lib/commonjs/components/AddToWalletButton.js +1 -1
  15. package/lib/commonjs/components/AddToWalletButton.js.map +1 -1
  16. package/lib/commonjs/components/AddressSheet.js +1 -1
  17. package/lib/commonjs/components/AddressSheet.js.map +1 -1
  18. package/lib/commonjs/components/AuBECSDebitForm.js +1 -1
  19. package/lib/commonjs/components/AuBECSDebitForm.js.map +1 -1
  20. package/lib/commonjs/components/CardField.js +1 -1
  21. package/lib/commonjs/components/CardField.js.map +1 -1
  22. package/lib/commonjs/components/CardForm.js +1 -1
  23. package/lib/commonjs/components/CardForm.js.map +1 -1
  24. package/lib/commonjs/components/PlatformPayButton.js +1 -1
  25. package/lib/commonjs/components/PlatformPayButton.js.map +1 -1
  26. package/lib/commonjs/components/StripeContainer.js +1 -1
  27. package/lib/commonjs/components/StripeContainer.js.map +1 -1
  28. package/lib/commonjs/connect/Components.js +1 -1
  29. package/lib/commonjs/connect/Components.js.map +1 -1
  30. package/lib/commonjs/connect/ConnectComponentsProvider.js +1 -1
  31. package/lib/commonjs/connect/ConnectComponentsProvider.js.map +1 -1
  32. package/lib/commonjs/connect/EmbeddedComponent.js +1 -1
  33. package/lib/commonjs/connect/EmbeddedComponent.js.map +1 -1
  34. package/lib/commonjs/connect/ModalCloseButton.js +1 -1
  35. package/lib/commonjs/connect/ModalCloseButton.js.map +1 -1
  36. package/lib/commonjs/connect/NavigationBar.js +1 -1
  37. package/lib/commonjs/connect/NavigationBar.js.map +1 -1
  38. package/lib/commonjs/events.js.map +1 -1
  39. package/lib/commonjs/helpers.js +1 -1
  40. package/lib/commonjs/hooks/useOnramp.js +1 -1
  41. package/lib/commonjs/hooks/useOnramp.js.map +1 -1
  42. package/lib/commonjs/specs/NativeAddToWalletButton.js +1 -1
  43. package/lib/commonjs/specs/NativeAddressSheet.js +1 -1
  44. package/lib/commonjs/specs/NativeApplePayButton.js +1 -1
  45. package/lib/commonjs/specs/NativeAuBECSDebitForm.js +1 -1
  46. package/lib/commonjs/specs/NativeCardField.js +1 -1
  47. package/lib/commonjs/specs/NativeCardField.js.map +1 -1
  48. package/lib/commonjs/specs/NativeCardForm.js +1 -1
  49. package/lib/commonjs/specs/NativeCardForm.js.map +1 -1
  50. package/lib/commonjs/specs/NativeConnectAccountOnboardingView.js +1 -1
  51. package/lib/commonjs/specs/NativeEmbeddedPaymentElement.js +1 -1
  52. package/lib/commonjs/specs/NativeEmbeddedPaymentElement.js.map +1 -1
  53. package/lib/commonjs/specs/NativeGooglePayButton.js +1 -1
  54. package/lib/commonjs/specs/NativeNavigationBar.js +1 -1
  55. package/lib/commonjs/specs/NativeStripeContainer.js +1 -1
  56. package/lib/commonjs/types/EmbeddedPaymentElement.js +1 -1
  57. package/lib/commonjs/types/EmbeddedPaymentElement.js.map +1 -1
  58. package/lib/module/components/AddToWalletButton.js +1 -1
  59. package/lib/module/components/AddToWalletButton.js.map +1 -1
  60. package/lib/module/components/AddressSheet.js +1 -1
  61. package/lib/module/components/AddressSheet.js.map +1 -1
  62. package/lib/module/components/AuBECSDebitForm.js +1 -1
  63. package/lib/module/components/AuBECSDebitForm.js.map +1 -1
  64. package/lib/module/components/CardField.js +1 -1
  65. package/lib/module/components/CardField.js.map +1 -1
  66. package/lib/module/components/CardForm.js +1 -1
  67. package/lib/module/components/CardForm.js.map +1 -1
  68. package/lib/module/components/PlatformPayButton.js +1 -1
  69. package/lib/module/components/PlatformPayButton.js.map +1 -1
  70. package/lib/module/components/StripeContainer.js +1 -1
  71. package/lib/module/components/StripeContainer.js.map +1 -1
  72. package/lib/module/connect/Components.js +1 -1
  73. package/lib/module/connect/Components.js.map +1 -1
  74. package/lib/module/connect/ConnectComponentsProvider.js +1 -1
  75. package/lib/module/connect/ConnectComponentsProvider.js.map +1 -1
  76. package/lib/module/connect/EmbeddedComponent.js +1 -1
  77. package/lib/module/connect/EmbeddedComponent.js.map +1 -1
  78. package/lib/module/connect/ModalCloseButton.js +1 -1
  79. package/lib/module/connect/ModalCloseButton.js.map +1 -1
  80. package/lib/module/connect/NavigationBar.js +1 -1
  81. package/lib/module/connect/NavigationBar.js.map +1 -1
  82. package/lib/module/events.js.map +1 -1
  83. package/lib/module/helpers.js +1 -1
  84. package/lib/module/hooks/useOnramp.js +1 -1
  85. package/lib/module/hooks/useOnramp.js.map +1 -1
  86. package/lib/module/specs/NativeAddToWalletButton.js +1 -1
  87. package/lib/module/specs/NativeAddressSheet.js +1 -1
  88. package/lib/module/specs/NativeApplePayButton.js +1 -1
  89. package/lib/module/specs/NativeAuBECSDebitForm.js +1 -1
  90. package/lib/module/specs/NativeCardField.js +1 -1
  91. package/lib/module/specs/NativeCardField.js.map +1 -1
  92. package/lib/module/specs/NativeCardForm.js +1 -1
  93. package/lib/module/specs/NativeCardForm.js.map +1 -1
  94. package/lib/module/specs/NativeConnectAccountOnboardingView.js +1 -1
  95. package/lib/module/specs/NativeEmbeddedPaymentElement.js +1 -1
  96. package/lib/module/specs/NativeEmbeddedPaymentElement.js.map +1 -1
  97. package/lib/module/specs/NativeGooglePayButton.js +1 -1
  98. package/lib/module/specs/NativeNavigationBar.js +1 -1
  99. package/lib/module/specs/NativeStripeContainer.js +1 -1
  100. package/lib/module/types/EmbeddedPaymentElement.js +1 -1
  101. package/lib/module/types/EmbeddedPaymentElement.js.map +1 -1
  102. package/lib/typescript/src/connect/EmbeddedComponent.d.ts.map +1 -1
  103. package/lib/typescript/src/events.d.ts +1 -0
  104. package/lib/typescript/src/events.d.ts.map +1 -1
  105. package/lib/typescript/src/hooks/useOnramp.d.ts +2 -1
  106. package/lib/typescript/src/hooks/useOnramp.d.ts.map +1 -1
  107. package/lib/typescript/src/specs/NativeEmbeddedPaymentElement.d.ts +1 -0
  108. package/lib/typescript/src/specs/NativeEmbeddedPaymentElement.d.ts.map +1 -1
  109. package/lib/typescript/src/types/EmbeddedPaymentElement.d.ts.map +1 -1
  110. package/package.json +13 -7
  111. package/src/connect/EmbeddedComponent.tsx +10 -10
  112. package/src/events.ts +1 -0
  113. package/src/hooks/useOnramp.tsx +5 -1
  114. package/src/specs/NativeEmbeddedPaymentElement.ts +5 -1
  115. package/src/types/EmbeddedPaymentElement.tsx +24 -3
package/README.md CHANGED
@@ -215,14 +215,6 @@ function App() {
215
215
 
216
216
  You can find more details about the `StripeProvider` component in the [API reference](https://stripe.dev/stripe-react-native/api-reference/index.html#StripeProvider).
217
217
 
218
- ##### Additional steps for webhook forwarding
219
-
220
- Certain payment methods require a [webhook listener](https://stripe.com/docs/payments/payment-intents/verifying-status#webhooks) to notify you of changes in the status. When developing locally, you can use the [Stripe CLI](https://stripe.com/docs/stripe-cli) to forward webhook events to your local dev server.
221
-
222
- - [Install the `stripe-cli`](https://stripe.com/docs/stripe-cli#install)
223
- - Run `stripe listen --forward-to localhost:4242/webhook`
224
- - The CLI will print a webhook secret (such as, `whsec_***`) to the console. Set STRIPE_WEBHOOK_SECRET to this value in your `example/.env` file.
225
-
226
218
  ## Testing
227
219
 
228
220
  This library includes a built in mock file for Jest.
@@ -183,6 +183,7 @@ dependencies {
183
183
  testImplementation "org.mockito:mockito-core:3.+"
184
184
  testImplementation "org.robolectric:robolectric:4.10"
185
185
  testImplementation "androidx.test:core:1.4.0"
186
+ testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.0"
186
187
 
187
188
  implementation "androidx.compose.ui:ui:1.7.8"
188
189
  implementation "androidx.compose.foundation:foundation-layout:1.7.8"
@@ -54,6 +54,10 @@ class EmbeddedPaymentElementView(
54
54
  val intentConfiguration: PaymentSheet.IntentConfiguration,
55
55
  ) : Event
56
56
 
57
+ data class Update(
58
+ val intentConfiguration: PaymentSheet.IntentConfiguration,
59
+ ) : Event
60
+
57
61
  data object Confirm : Event
58
62
 
59
63
  data object ClearPaymentOption : Event
@@ -322,6 +326,47 @@ class EmbeddedPaymentElementView(
322
326
  }
323
327
  }
324
328
 
329
+ is Event.Update -> {
330
+ val elemConfig = latestElementConfig
331
+ if (elemConfig == null) {
332
+ val payload =
333
+ Arguments.createMap().apply {
334
+ putString("message", "Cannot update: no element configuration exists")
335
+ }
336
+ requireStripeSdkModule().eventEmitter.emitEmbeddedPaymentElementLoadingFailed(payload)
337
+ requireStripeSdkModule().eventEmitter.emitEmbeddedPaymentElementUpdateComplete(null)
338
+ return@collect
339
+ }
340
+
341
+ val result =
342
+ embedded.configure(
343
+ intentConfiguration = ev.intentConfiguration,
344
+ configuration = elemConfig,
345
+ )
346
+
347
+ when (result) {
348
+ is EmbeddedPaymentElement.ConfigureResult.Succeeded -> {
349
+ val payload =
350
+ Arguments.createMap().apply {
351
+ putString("status", "succeeded")
352
+ }
353
+ requireStripeSdkModule().eventEmitter.emitEmbeddedPaymentElementUpdateComplete(payload)
354
+ }
355
+ is EmbeddedPaymentElement.ConfigureResult.Failed -> {
356
+ val err = result.error
357
+ val msg = err.localizedMessage ?: err.toString()
358
+ val failPayload =
359
+ Arguments.createMap().apply {
360
+ putString("message", msg)
361
+ }
362
+ requireStripeSdkModule().eventEmitter.emitEmbeddedPaymentElementLoadingFailed(failPayload)
363
+ requireStripeSdkModule().eventEmitter.emitEmbeddedPaymentElementUpdateComplete(null)
364
+ }
365
+ }
366
+
367
+ latestIntentConfig = ev.intentConfiguration
368
+ }
369
+
325
370
  is Event.Confirm -> {
326
371
  embedded.confirm()
327
372
  }
@@ -412,6 +457,10 @@ class EmbeddedPaymentElementView(
412
457
  events.trySend(Event.Configure(config, intentConfig))
413
458
  }
414
459
 
460
+ fun update(intentConfig: PaymentSheet.IntentConfiguration) {
461
+ events.trySend(Event.Update(intentConfig))
462
+ }
463
+
415
464
  fun confirm() {
416
465
  events.trySend(Event.Confirm)
417
466
  }
@@ -2,8 +2,11 @@ package com.reactnativestripesdk
2
2
 
3
3
  import android.annotation.SuppressLint
4
4
  import android.content.Context
5
+ import com.facebook.react.bridge.Arguments
5
6
  import com.facebook.react.bridge.Dynamic
6
7
  import com.facebook.react.bridge.ReadableMap
8
+ import com.facebook.react.bridge.WritableArray
9
+ import com.facebook.react.bridge.WritableMap
7
10
  import com.facebook.react.module.annotations.ReactModule
8
11
  import com.facebook.react.uimanager.ThemedReactContext
9
12
  import com.facebook.react.uimanager.ViewGroupManager
@@ -23,6 +26,8 @@ import com.stripe.android.ExperimentalAllowsRemovalOfLastSavedPaymentMethodApi
23
26
  import com.stripe.android.paymentelement.EmbeddedPaymentElement
24
27
  import com.stripe.android.paymentelement.ExperimentalCustomPaymentMethodsApi
25
28
  import com.stripe.android.paymentsheet.PaymentSheet
29
+ import org.json.JSONArray
30
+ import org.json.JSONObject
26
31
 
27
32
  @ReactModule(name = EmbeddedPaymentElementViewManager.NAME)
28
33
  class EmbeddedPaymentElementViewManager :
@@ -178,6 +183,62 @@ class EmbeddedPaymentElementViewManager :
178
183
  override fun clearPaymentOption(view: EmbeddedPaymentElementView) {
179
184
  view.clearPaymentOption()
180
185
  }
186
+
187
+ override fun update(
188
+ view: EmbeddedPaymentElementView,
189
+ intentConfigurationJson: String?,
190
+ ) {
191
+ intentConfigurationJson?.let { json ->
192
+ try {
193
+ val jsonObject = JSONObject(json)
194
+ val cfg = jsonToWritableMap(jsonObject)
195
+ val intentConfig = buildIntentConfiguration(cfg)
196
+ if (intentConfig != null) {
197
+ view.update(intentConfig)
198
+ }
199
+ } catch (e: Exception) {
200
+ android.util.Log.e("EmbeddedPaymentElement", "Failed to parse intent config JSON", e)
201
+ }
202
+ }
203
+ }
204
+
205
+ private fun jsonToWritableMap(json: JSONObject): WritableMap {
206
+ val map = Arguments.createMap()
207
+ val keys = json.keys()
208
+ while (keys.hasNext()) {
209
+ val key = keys.next()
210
+ when (val value = json.get(key)) {
211
+ is Boolean -> map.putBoolean(key, value)
212
+ is Int -> map.putInt(key, value)
213
+ is Double -> map.putDouble(key, value)
214
+ is Long -> map.putDouble(key, value.toDouble())
215
+ is String -> map.putString(key, value)
216
+ is JSONObject -> map.putMap(key, jsonToWritableMap(value))
217
+ is JSONArray -> map.putArray(key, jsonToWritableArray(value))
218
+ JSONObject.NULL -> map.putNull(key)
219
+ else -> map.putString(key, value.toString())
220
+ }
221
+ }
222
+ return map
223
+ }
224
+
225
+ private fun jsonToWritableArray(json: JSONArray): WritableArray {
226
+ val array = Arguments.createArray()
227
+ for (i in 0 until json.length()) {
228
+ when (val value = json.get(i)) {
229
+ is Boolean -> array.pushBoolean(value)
230
+ is Int -> array.pushInt(value)
231
+ is Double -> array.pushDouble(value)
232
+ is Long -> array.pushDouble(value.toDouble())
233
+ is String -> array.pushString(value)
234
+ is JSONObject -> array.pushMap(jsonToWritableMap(value))
235
+ is JSONArray -> array.pushArray(jsonToWritableArray(value))
236
+ JSONObject.NULL -> array.pushNull()
237
+ else -> array.pushString(value.toString())
238
+ }
239
+ }
240
+ return array
241
+ }
181
242
  }
182
243
 
183
244
  internal fun mapToRowSelectionBehaviorType(map: ReadableMap?): RowSelectionBehaviorType {
@@ -84,6 +84,10 @@ class EventEmitterCompat(
84
84
  invoke("embeddedPaymentElementLoadingFailed", value)
85
85
  }
86
86
 
87
+ fun emitEmbeddedPaymentElementUpdateComplete(value: ReadableMap?) {
88
+ invoke("embeddedPaymentElementUpdateComplete", value)
89
+ }
90
+
87
91
  fun emitOnCustomPaymentMethodConfirmHandlerCallback(value: ReadableMap?) {
88
92
  invoke("onCustomPaymentMethodConfirmHandlerCallback", value)
89
93
  }
@@ -58,8 +58,11 @@ import kotlinx.coroutines.CoroutineScope
58
58
  import kotlinx.coroutines.Dispatchers
59
59
  import kotlinx.coroutines.delay
60
60
  import kotlinx.coroutines.launch
61
+ import kotlinx.coroutines.suspendCancellableCoroutine
62
+ import kotlinx.coroutines.withTimeoutOrNull
61
63
  import java.io.ByteArrayOutputStream
62
64
  import kotlin.Exception
65
+ import kotlin.coroutines.resume
63
66
 
64
67
  @OptIn(
65
68
  ReactNativeSdkInternal::class,
@@ -140,28 +143,42 @@ class PaymentSheetManager(
140
143
 
141
144
  val paymentOptionCallback =
142
145
  PaymentOptionResultCallback { paymentOptionResult ->
143
- val result =
144
- paymentOptionResult.paymentOption?.let {
145
- val bitmap = getBitmapFromDrawable(it.icon())
146
- val imageString = getBase64FromBitmap(bitmap)
146
+ paymentOptionResult.paymentOption?.let { paymentOption ->
147
+ // Convert drawable to bitmap asynchronously to avoid shared state issues
148
+ CoroutineScope(Dispatchers.Default).launch {
149
+ val imageString =
150
+ try {
151
+ convertDrawableToBase64(paymentOption.icon())
152
+ } catch (e: Exception) {
153
+ val result =
154
+ createError(
155
+ PaymentSheetErrorType.Failed.toString(),
156
+ "Failed to process payment option image: ${e.message}",
157
+ )
158
+ resolvePresentPromise(result)
159
+ return@launch
160
+ }
161
+
147
162
  val option: WritableMap = Arguments.createMap()
148
- option.putString("label", it.label)
163
+ option.putString("label", paymentOption.label)
149
164
  option.putString("image", imageString)
150
165
  val additionalFields: Map<String, Any> = mapOf("didCancel" to paymentOptionResult.didCancel)
151
- createResult("paymentOption", option, additionalFields)
166
+ val result = createResult("paymentOption", option, additionalFields)
167
+ resolvePresentPromise(result)
152
168
  }
153
- ?: run {
154
- if (paymentSheetTimedOut) {
155
- paymentSheetTimedOut = false
156
- createError(PaymentSheetErrorType.Timeout.toString(), "The payment has timed out")
157
- } else {
158
- createError(
159
- PaymentSheetErrorType.Canceled.toString(),
160
- "The payment option selection flow has been canceled",
161
- )
162
- }
169
+ } ?: run {
170
+ val result =
171
+ if (paymentSheetTimedOut) {
172
+ paymentSheetTimedOut = false
173
+ createError(PaymentSheetErrorType.Timeout.toString(), "The payment has timed out")
174
+ } else {
175
+ createError(
176
+ PaymentSheetErrorType.Canceled.toString(),
177
+ "The payment option selection flow has been canceled",
178
+ )
163
179
  }
164
- resolvePresentPromise(result)
180
+ resolvePresentPromise(result)
181
+ }
165
182
  }
166
183
 
167
184
  val paymentResultCallback =
@@ -413,16 +430,31 @@ class PaymentSheetManager(
413
430
  private fun configureFlowController() {
414
431
  val onFlowControllerConfigure =
415
432
  PaymentSheet.FlowController.ConfigCallback { _, _ ->
416
- val result =
417
- flowController?.getPaymentOption()?.let {
418
- val bitmap = getBitmapFromDrawable(it.icon())
419
- val imageString = getBase64FromBitmap(bitmap)
433
+ flowController?.getPaymentOption()?.let { paymentOption ->
434
+ // Launch async job to convert drawable, but resolve promise synchronously
435
+ CoroutineScope(Dispatchers.Default).launch {
436
+ val imageString =
437
+ try {
438
+ convertDrawableToBase64(paymentOption.icon())
439
+ } catch (e: Exception) {
440
+ val result =
441
+ createError(
442
+ PaymentSheetErrorType.Failed.toString(),
443
+ "Failed to process payment option image: ${e.message}",
444
+ )
445
+ initPromise.resolve(result)
446
+ return@launch
447
+ }
448
+
420
449
  val option: WritableMap = Arguments.createMap()
421
- option.putString("label", it.label)
450
+ option.putString("label", paymentOption.label)
422
451
  option.putString("image", imageString)
423
- createResult("paymentOption", option)
424
- } ?: run { Arguments.createMap() }
425
- initPromise.resolve(result)
452
+ val result = createResult("paymentOption", option)
453
+ initPromise.resolve(result)
454
+ }
455
+ } ?: run {
456
+ initPromise.resolve(Arguments.createMap())
457
+ }
426
458
  }
427
459
 
428
460
  if (!paymentIntentClientSecret.isNullOrEmpty()) {
@@ -550,17 +582,86 @@ class PaymentSheetManager(
550
582
  }
551
583
  }
552
584
 
585
+ suspend fun waitForDrawableToLoad(
586
+ drawable: Drawable,
587
+ timeoutMs: Long = 3000,
588
+ ): Drawable {
589
+ // If already loaded, return immediately
590
+ if (drawable.intrinsicWidth > 1 && drawable.intrinsicHeight > 1) {
591
+ return drawable
592
+ }
593
+
594
+ // Use callback to be notified when drawable finishes loading
595
+ return withTimeoutOrNull(timeoutMs) {
596
+ suspendCancellableCoroutine { continuation ->
597
+ val callback =
598
+ object : Drawable.Callback {
599
+ override fun invalidateDrawable(who: Drawable) {
600
+ // Drawable has changed/loaded - check if it's ready now
601
+ if (who.intrinsicWidth > 1 && who.intrinsicHeight > 1) {
602
+ who.callback = null // Remove callback
603
+ if (continuation.isActive) {
604
+ continuation.resume(who)
605
+ }
606
+ }
607
+ }
608
+
609
+ override fun scheduleDrawable(
610
+ who: Drawable,
611
+ what: Runnable,
612
+ `when`: Long,
613
+ ) {}
614
+
615
+ override fun unscheduleDrawable(
616
+ who: Drawable,
617
+ what: Runnable,
618
+ ) {}
619
+ }
620
+
621
+ drawable.callback = callback
622
+
623
+ // Trigger an invalidation to check if it loads immediately
624
+ drawable.invalidateSelf()
625
+
626
+ continuation.invokeOnCancellation { drawable.callback = null }
627
+ }
628
+ } ?: drawable // Return drawable even if timeout (best effort)
629
+ }
630
+
631
+ suspend fun convertDrawableToBase64(drawable: Drawable): String? {
632
+ val loadedDrawable = waitForDrawableToLoad(drawable)
633
+ val bitmap = getBitmapFromDrawable(loadedDrawable)
634
+ return getBase64FromBitmap(bitmap)
635
+ }
636
+
553
637
  fun getBitmapFromDrawable(drawable: Drawable): Bitmap? {
554
638
  val drawableCompat = DrawableCompat.wrap(drawable).mutate()
555
- if (drawableCompat.intrinsicWidth <= 0 || drawableCompat.intrinsicHeight <= 0) {
639
+
640
+ // Determine the size to use - prefer intrinsic size, fall back to bounds
641
+ val width =
642
+ if (drawableCompat.intrinsicWidth > 0) {
643
+ drawableCompat.intrinsicWidth
644
+ } else {
645
+ drawableCompat.bounds.width()
646
+ }
647
+
648
+ val height =
649
+ if (drawableCompat.intrinsicHeight > 0) {
650
+ drawableCompat.intrinsicHeight
651
+ } else {
652
+ drawableCompat.bounds.height()
653
+ }
654
+
655
+ if (width <= 0 || height <= 0) {
556
656
  return null
557
657
  }
558
- val bitmap =
559
- createBitmap(drawableCompat.intrinsicWidth, drawableCompat.intrinsicHeight)
658
+
659
+ val bitmap = createBitmap(width, height, Bitmap.Config.ARGB_8888)
560
660
  bitmap.eraseColor(Color.TRANSPARENT)
561
661
  val canvas = Canvas(bitmap)
562
- drawable.setBounds(0, 0, canvas.width, canvas.height)
563
- drawable.draw(canvas)
662
+ drawableCompat.setBounds(0, 0, canvas.width, canvas.height)
663
+ drawableCompat.draw(canvas)
664
+
564
665
  return bitmap
565
666
  }
566
667
 
@@ -17,8 +17,7 @@ import com.reactnativestripesdk.ReactNativeCustomerSessionProvider
17
17
  import com.reactnativestripesdk.buildBillingDetails
18
18
  import com.reactnativestripesdk.buildBillingDetailsCollectionConfiguration
19
19
  import com.reactnativestripesdk.buildPaymentSheetAppearance
20
- import com.reactnativestripesdk.getBase64FromBitmap
21
- import com.reactnativestripesdk.getBitmapFromDrawable
20
+ import com.reactnativestripesdk.convertDrawableToBase64
22
21
  import com.reactnativestripesdk.mapToCardBrandAcceptance
23
22
  import com.reactnativestripesdk.utils.CreateTokenErrorType
24
23
  import com.reactnativestripesdk.utils.ErrorType
@@ -163,25 +162,49 @@ class CustomerSheetManager(
163
162
  }
164
163
 
165
164
  private fun handleResult(result: CustomerSheetResult) {
166
- var promiseResult = Arguments.createMap()
167
165
  when (result) {
168
166
  is CustomerSheetResult.Failed -> {
169
167
  resolvePresentPromise(createError(ErrorType.Failed.toString(), result.exception))
170
168
  }
171
169
 
172
170
  is CustomerSheetResult.Selected -> {
173
- promiseResult = createPaymentOptionResult(result.selection)
171
+ // Convert drawable asynchronously to avoid shared state issues
172
+ CoroutineScope(Dispatchers.Default).launch {
173
+ try {
174
+ val promiseResult = createPaymentOptionResult(result.selection)
175
+ resolvePresentPromise(promiseResult)
176
+ } catch (e: Exception) {
177
+ resolvePresentPromise(
178
+ createError(
179
+ ErrorType.Failed.toString(),
180
+ "Failed to process payment option image: ${e.message}",
181
+ ),
182
+ )
183
+ }
184
+ }
174
185
  }
175
186
 
176
187
  is CustomerSheetResult.Canceled -> {
177
- promiseResult = createPaymentOptionResult(result.selection)
178
- promiseResult.putMap(
179
- "error",
180
- Arguments.createMap().also { it.putString("code", ErrorType.Canceled.toString()) },
181
- )
188
+ // Convert drawable asynchronously to avoid shared state issues
189
+ CoroutineScope(Dispatchers.Default).launch {
190
+ try {
191
+ val promiseResult = createPaymentOptionResult(result.selection)
192
+ promiseResult.putMap(
193
+ "error",
194
+ Arguments.createMap().also { it.putString("code", ErrorType.Canceled.toString()) },
195
+ )
196
+ resolvePresentPromise(promiseResult)
197
+ } catch (e: Exception) {
198
+ resolvePresentPromise(
199
+ createError(
200
+ ErrorType.Failed.toString(),
201
+ "Failed to process payment option image: ${e.message}",
202
+ ),
203
+ )
204
+ }
205
+ }
182
206
  }
183
207
  }
184
- resolvePresentPromise(promiseResult)
185
208
  }
186
209
 
187
210
  override fun onPresent() {
@@ -355,7 +378,7 @@ class CustomerSheetManager(
355
378
  )
356
379
  }
357
380
 
358
- internal fun createPaymentOptionResult(selection: PaymentOptionSelection?): WritableMap {
381
+ internal suspend fun createPaymentOptionResult(selection: PaymentOptionSelection?): WritableMap {
359
382
  var paymentOptionResult = Arguments.createMap()
360
383
 
361
384
  when (selection) {
@@ -392,16 +415,18 @@ class CustomerSheetManager(
392
415
  }.build()
393
416
  }
394
417
 
395
- private fun buildResult(
418
+ private suspend fun buildResult(
396
419
  label: String,
397
420
  drawable: Drawable,
398
421
  paymentMethod: PaymentMethod?,
399
422
  ): WritableMap {
423
+ val imageString = convertDrawableToBase64(drawable)
424
+
400
425
  val result = Arguments.createMap()
401
426
  val paymentOption =
402
427
  Arguments.createMap().also {
403
428
  it.putString("label", label)
404
- it.putString("image", getBase64FromBitmap(getBitmapFromDrawable(drawable)))
429
+ it.putString("image", imageString)
405
430
  }
406
431
  result.putMap("paymentOption", paymentOption)
407
432
  if (paymentMethod != null) {
@@ -44,6 +44,9 @@ public class EmbeddedPaymentElementViewManagerDelegate<T extends View, U extends
44
44
  case "clearPaymentOption":
45
45
  mViewManager.clearPaymentOption(view);
46
46
  break;
47
+ case "update":
48
+ mViewManager.update(view, args != null ? args.getString(0) : null);
49
+ break;
47
50
  }
48
51
  }
49
52
  }
@@ -10,6 +10,7 @@
10
10
  package com.facebook.react.viewmanagers;
11
11
 
12
12
  import android.view.View;
13
+ import androidx.annotation.Nullable;
13
14
  import com.facebook.react.bridge.Dynamic;
14
15
 
15
16
  public interface EmbeddedPaymentElementViewManagerInterface<T extends View> {
@@ -17,4 +18,5 @@ public interface EmbeddedPaymentElementViewManagerInterface<T extends View> {
17
18
  void setIntentConfiguration(T view, Dynamic value);
18
19
  void confirm(T view);
19
20
  void clearPaymentOption(T view);
21
+ void update(T view, @Nullable String intentConfigurationJson);
20
22
  }