@stripe/stripe-react-native 0.57.2 → 0.58.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.
Files changed (147) hide show
  1. package/android/build.gradle +1 -0
  2. package/android/gradle.properties +1 -1
  3. package/android/src/main/java/com/reactnativestripesdk/EmbeddedPaymentElementView.kt +0 -3
  4. package/android/src/main/java/com/reactnativestripesdk/EmbeddedPaymentElementViewManager.kt +5 -3
  5. package/android/src/main/java/com/reactnativestripesdk/NavigationBarView.kt +12 -1
  6. package/android/src/main/java/com/reactnativestripesdk/PaymentElementConfig.kt +18 -0
  7. package/android/src/main/java/com/reactnativestripesdk/PaymentSheetManager.kt +136 -35
  8. package/android/src/main/java/com/reactnativestripesdk/StripeAbstractComposeView.kt +17 -5
  9. package/android/src/main/java/com/reactnativestripesdk/StripeSdkModule.kt +9 -2
  10. package/android/src/main/java/com/reactnativestripesdk/customersheet/CustomerSheetManager.kt +38 -13
  11. package/android/src/main/java/com/reactnativestripesdk/utils/Mappers.kt +0 -2
  12. package/android/src/test/java/com/reactnativestripesdk/DrawableConversionPropertyTest.kt +224 -0
  13. package/android/src/test/java/com/reactnativestripesdk/DrawableConversionTest.kt +146 -0
  14. package/android/src/test/java/com/reactnativestripesdk/DrawableLoadingTest.kt +150 -0
  15. package/android/src/test/java/com/reactnativestripesdk/PaymentElementConfigTest.kt +138 -1
  16. package/android/src/test/java/com/reactnativestripesdk/PaymentOptionImageConsistencyTest.kt +186 -0
  17. package/ios/ConnectAccountOnboarding/ConnectAccountOnboardingView.swift +13 -19
  18. package/ios/StripeSdkImpl+Embedded.swift +4 -1
  19. package/ios/StripeSdkImpl+PaymentSheet.swift +28 -1
  20. package/ios/StripeSdkImpl.swift +26 -2
  21. package/jest/mock.js +6 -0
  22. package/jest/setup.js +30 -0
  23. package/lib/commonjs/components/AddToWalletButton.js +1 -1
  24. package/lib/commonjs/components/AddToWalletButton.js.map +1 -1
  25. package/lib/commonjs/components/AddressSheet.js +1 -1
  26. package/lib/commonjs/components/AddressSheet.js.map +1 -1
  27. package/lib/commonjs/components/AuBECSDebitForm.js +1 -1
  28. package/lib/commonjs/components/AuBECSDebitForm.js.map +1 -1
  29. package/lib/commonjs/components/CardField.js +1 -1
  30. package/lib/commonjs/components/CardField.js.map +1 -1
  31. package/lib/commonjs/components/CardForm.js +1 -1
  32. package/lib/commonjs/components/CardForm.js.map +1 -1
  33. package/lib/commonjs/components/PlatformPayButton.js +1 -1
  34. package/lib/commonjs/components/PlatformPayButton.js.map +1 -1
  35. package/lib/commonjs/components/StripeContainer.js +1 -1
  36. package/lib/commonjs/components/StripeContainer.js.map +1 -1
  37. package/lib/commonjs/connect/Components.js +1 -1
  38. package/lib/commonjs/connect/Components.js.map +1 -1
  39. package/lib/commonjs/connect/ConnectComponentsProvider.js +1 -1
  40. package/lib/commonjs/connect/ConnectComponentsProvider.js.map +1 -1
  41. package/lib/commonjs/connect/EmbeddedComponent.js +9 -4
  42. package/lib/commonjs/connect/EmbeddedComponent.js.map +1 -1
  43. package/lib/commonjs/connect/ModalCloseButton.js +1 -1
  44. package/lib/commonjs/connect/ModalCloseButton.js.map +1 -1
  45. package/lib/commonjs/connect/NavigationBar.js +1 -1
  46. package/lib/commonjs/connect/NavigationBar.js.map +1 -1
  47. package/lib/commonjs/helpers.js +1 -1
  48. package/lib/commonjs/hooks/useOnramp.js +1 -1
  49. package/lib/commonjs/hooks/useOnramp.js.map +1 -1
  50. package/lib/commonjs/specs/NativeAddToWalletButton.js +1 -1
  51. package/lib/commonjs/specs/NativeAddressSheet.js +1 -1
  52. package/lib/commonjs/specs/NativeApplePayButton.js +1 -1
  53. package/lib/commonjs/specs/NativeAuBECSDebitForm.js +1 -1
  54. package/lib/commonjs/specs/NativeCardField.js +1 -1
  55. package/lib/commonjs/specs/NativeCardField.js.map +1 -1
  56. package/lib/commonjs/specs/NativeCardForm.js +1 -1
  57. package/lib/commonjs/specs/NativeCardForm.js.map +1 -1
  58. package/lib/commonjs/specs/NativeConnectAccountOnboardingView.js +1 -1
  59. package/lib/commonjs/specs/NativeEmbeddedPaymentElement.js +1 -1
  60. package/lib/commonjs/specs/NativeEmbeddedPaymentElement.js.map +1 -1
  61. package/lib/commonjs/specs/NativeGooglePayButton.js +1 -1
  62. package/lib/commonjs/specs/NativeNavigationBar.js +1 -1
  63. package/lib/commonjs/specs/NativeStripeContainer.js +1 -1
  64. package/lib/commonjs/types/EmbeddedPaymentElement.js +1 -1
  65. package/lib/commonjs/types/EmbeddedPaymentElement.js.map +1 -1
  66. package/lib/commonjs/types/FinancialConnections.js.map +1 -1
  67. package/lib/commonjs/types/PaymentSheet.js +1 -1
  68. package/lib/commonjs/types/PaymentSheet.js.map +1 -1
  69. package/lib/module/components/AddToWalletButton.js +1 -1
  70. package/lib/module/components/AddToWalletButton.js.map +1 -1
  71. package/lib/module/components/AddressSheet.js +1 -1
  72. package/lib/module/components/AddressSheet.js.map +1 -1
  73. package/lib/module/components/AuBECSDebitForm.js +1 -1
  74. package/lib/module/components/AuBECSDebitForm.js.map +1 -1
  75. package/lib/module/components/CardField.js +1 -1
  76. package/lib/module/components/CardField.js.map +1 -1
  77. package/lib/module/components/CardForm.js +1 -1
  78. package/lib/module/components/CardForm.js.map +1 -1
  79. package/lib/module/components/PlatformPayButton.js +1 -1
  80. package/lib/module/components/PlatformPayButton.js.map +1 -1
  81. package/lib/module/components/StripeContainer.js +1 -1
  82. package/lib/module/components/StripeContainer.js.map +1 -1
  83. package/lib/module/connect/Components.js +1 -1
  84. package/lib/module/connect/Components.js.map +1 -1
  85. package/lib/module/connect/ConnectComponentsProvider.js +1 -1
  86. package/lib/module/connect/ConnectComponentsProvider.js.map +1 -1
  87. package/lib/module/connect/EmbeddedComponent.js +9 -4
  88. package/lib/module/connect/EmbeddedComponent.js.map +1 -1
  89. package/lib/module/connect/ModalCloseButton.js +1 -1
  90. package/lib/module/connect/ModalCloseButton.js.map +1 -1
  91. package/lib/module/connect/NavigationBar.js +1 -1
  92. package/lib/module/connect/NavigationBar.js.map +1 -1
  93. package/lib/module/helpers.js +1 -1
  94. package/lib/module/hooks/useOnramp.js +1 -1
  95. package/lib/module/hooks/useOnramp.js.map +1 -1
  96. package/lib/module/specs/NativeAddToWalletButton.js +1 -1
  97. package/lib/module/specs/NativeAddressSheet.js +1 -1
  98. package/lib/module/specs/NativeApplePayButton.js +1 -1
  99. package/lib/module/specs/NativeAuBECSDebitForm.js +1 -1
  100. package/lib/module/specs/NativeCardField.js +1 -1
  101. package/lib/module/specs/NativeCardField.js.map +1 -1
  102. package/lib/module/specs/NativeCardForm.js +1 -1
  103. package/lib/module/specs/NativeCardForm.js.map +1 -1
  104. package/lib/module/specs/NativeConnectAccountOnboardingView.js +1 -1
  105. package/lib/module/specs/NativeEmbeddedPaymentElement.js +1 -1
  106. package/lib/module/specs/NativeEmbeddedPaymentElement.js.map +1 -1
  107. package/lib/module/specs/NativeGooglePayButton.js +1 -1
  108. package/lib/module/specs/NativeNavigationBar.js +1 -1
  109. package/lib/module/specs/NativeStripeContainer.js +1 -1
  110. package/lib/module/types/EmbeddedPaymentElement.js +1 -1
  111. package/lib/module/types/EmbeddedPaymentElement.js.map +1 -1
  112. package/lib/module/types/FinancialConnections.js.map +1 -1
  113. package/lib/module/types/PaymentSheet.js +1 -1
  114. package/lib/module/types/PaymentSheet.js.map +1 -1
  115. package/lib/typescript/src/connect/Components.d.ts.map +1 -1
  116. package/lib/typescript/src/connect/EmbeddedComponent.d.ts.map +1 -1
  117. package/lib/typescript/src/connect/connectTypes.d.ts +5 -1
  118. package/lib/typescript/src/connect/connectTypes.d.ts.map +1 -1
  119. package/lib/typescript/src/hooks/useOnramp.d.ts +2 -1
  120. package/lib/typescript/src/hooks/useOnramp.d.ts.map +1 -1
  121. package/lib/typescript/src/types/EmbeddedPaymentElement.d.ts +6 -1
  122. package/lib/typescript/src/types/EmbeddedPaymentElement.d.ts.map +1 -1
  123. package/lib/typescript/src/types/FinancialConnections.d.ts +2 -0
  124. package/lib/typescript/src/types/FinancialConnections.d.ts.map +1 -1
  125. package/lib/typescript/src/types/PaymentSheet.d.ts +30 -0
  126. package/lib/typescript/src/types/PaymentSheet.d.ts.map +1 -1
  127. package/package.json +15 -5
  128. package/src/connect/Components.tsx +18 -11
  129. package/src/connect/EmbeddedComponent.tsx +223 -12
  130. package/src/connect/connectTypes.ts +5 -1
  131. package/src/hooks/useOnramp.tsx +5 -1
  132. package/src/types/EmbeddedPaymentElement.tsx +6 -1
  133. package/src/types/FinancialConnections.ts +2 -0
  134. package/src/types/PaymentSheet.ts +32 -0
  135. package/stripe-react-native.podspec +1 -1
  136. package/android/.idea/AndroidProjectSystem.xml +0 -6
  137. package/android/.idea/caches/deviceStreaming.xml +0 -1029
  138. package/android/.idea/compiler.xml +0 -6
  139. package/android/.idea/gradle.xml +0 -19
  140. package/android/.idea/migrations.xml +0 -10
  141. package/android/.idea/misc.xml +0 -10
  142. package/android/.idea/runConfigurations.xml +0 -17
  143. package/android/.idea/vcs.xml +0 -6
  144. package/android/local.properties +0 -8
  145. package/ios/StripeSdk.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -7
  146. package/ios/StripeSdk.xcodeproj/project.xcworkspace/xcuserdata/tianzhao.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  147. package/ios/StripeSdk.xcodeproj/xcuserdata/tianzhao.xcuserdatad/xcschemes/xcschememanagement.plist +0 -19
@@ -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"
@@ -3,4 +3,4 @@ StripeSdk_compileSdkVersion=30
3
3
  StripeSdk_targetSdkVersion=28
4
4
  StripeSdk_minSdkVersion=21
5
5
  # Keep StripeSdk_stripeVersion in sync with https://github.com/stripe/stripe-identity-react-native/blob/main/android/gradle.properties
6
- StripeSdk_stripeVersion=22.2.+
6
+ StripeSdk_stripeVersion=22.7.+
@@ -29,7 +29,6 @@ import com.stripe.android.model.PaymentMethod
29
29
  import com.stripe.android.paymentelement.CustomPaymentMethodResult
30
30
  import com.stripe.android.paymentelement.CustomPaymentMethodResultHandler
31
31
  import com.stripe.android.paymentelement.EmbeddedPaymentElement
32
- import com.stripe.android.paymentelement.ExperimentalCustomPaymentMethodsApi
33
32
  import com.stripe.android.paymentelement.rememberEmbeddedPaymentElement
34
33
  import com.stripe.android.paymentsheet.CreateIntentResult
35
34
  import com.stripe.android.paymentsheet.PaymentSheet
@@ -44,7 +43,6 @@ enum class RowSelectionBehaviorType {
44
43
  ImmediateAction,
45
44
  }
46
45
 
47
- @OptIn(ExperimentalCustomPaymentMethodsApi::class)
48
46
  class EmbeddedPaymentElementView(
49
47
  context: Context,
50
48
  ) : StripeAbstractComposeView(context) {
@@ -77,7 +75,6 @@ class EmbeddedPaymentElementView(
77
75
  }
78
76
 
79
77
  @SuppressLint("RestrictedApi")
80
- @OptIn(ExperimentalCustomPaymentMethodsApi::class)
81
78
  @Composable
82
79
  override fun Content() {
83
80
  val type by remember { rowSelectionBehaviorType }
@@ -24,7 +24,7 @@ import com.reactnativestripesdk.utils.mapToPreferredNetworks
24
24
  import com.reactnativestripesdk.utils.parseCustomPaymentMethods
25
25
  import com.stripe.android.ExperimentalAllowsRemovalOfLastSavedPaymentMethodApi
26
26
  import com.stripe.android.paymentelement.EmbeddedPaymentElement
27
- import com.stripe.android.paymentelement.ExperimentalCustomPaymentMethodsApi
27
+ import com.stripe.android.paymentsheet.CardFundingFilteringPrivatePreview
28
28
  import com.stripe.android.paymentsheet.PaymentSheet
29
29
  import org.json.JSONArray
30
30
  import org.json.JSONObject
@@ -98,7 +98,7 @@ class EmbeddedPaymentElementViewManager :
98
98
  @SuppressLint("RestrictedApi")
99
99
  @OptIn(
100
100
  ExperimentalAllowsRemovalOfLastSavedPaymentMethodApi::class,
101
- ExperimentalCustomPaymentMethodsApi::class,
101
+ CardFundingFilteringPrivatePreview::class,
102
102
  )
103
103
  private fun parseElementConfiguration(
104
104
  map: ReadableMap,
@@ -157,7 +157,9 @@ class EmbeddedPaymentElementViewManager :
157
157
  ),
158
158
  ).allowsRemovalOfLastSavedPaymentMethod(allowsRemovalOfLastSavedPaymentMethod)
159
159
  .cardBrandAcceptance(mapToCardBrandAcceptance(map))
160
- .embeddedViewDisplaysMandateText(
160
+ .apply {
161
+ mapToAllowedCardFundingTypes(map)?.let { allowedCardFundingTypes(it) }
162
+ }.embeddedViewDisplaysMandateText(
161
163
  map.getBooleanOr("embeddedViewDisplaysMandateText", true),
162
164
  ).customPaymentMethods(
163
165
  parseCustomPaymentMethods(
@@ -2,7 +2,11 @@ package com.reactnativestripesdk
2
2
 
3
3
  import android.annotation.SuppressLint
4
4
  import android.graphics.Color
5
+ import android.graphics.PorterDuff
6
+ import android.graphics.PorterDuffColorFilter
7
+ import android.os.Build
5
8
  import android.view.Gravity
9
+ import android.view.View.MeasureSpec
6
10
  import android.widget.FrameLayout
7
11
  import android.widget.ImageButton
8
12
  import android.widget.TextView
@@ -18,6 +22,7 @@ class NavigationBarView(
18
22
  ) : FrameLayout(context) {
19
23
  private val toolbar: Toolbar
20
24
  private val titleTextView: TextView
25
+ private val closeButton: ImageButton
21
26
  private var titleText: String? = null
22
27
 
23
28
  init {
@@ -53,7 +58,7 @@ class NavigationBarView(
53
58
  toolbar.addView(titleTextView, titleParams)
54
59
 
55
60
  // Create close button
56
- val closeButton =
61
+ closeButton =
57
62
  ImageButton(context).apply {
58
63
  setImageDrawable(
59
64
  context.resources.getDrawable(
@@ -61,6 +66,12 @@ class NavigationBarView(
61
66
  null,
62
67
  ),
63
68
  )
69
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
70
+ drawable?.setColorFilter(android.graphics.BlendModeColorFilter(Color.BLACK, android.graphics.BlendMode.SRC_IN))
71
+ } else {
72
+ @Suppress("DEPRECATION")
73
+ drawable?.setColorFilter(PorterDuffColorFilter(Color.BLACK, PorterDuff.Mode.SRC_IN))
74
+ }
64
75
  setBackgroundColor(Color.TRANSPARENT)
65
76
  setOnClickListener {
66
77
  dispatchCloseButtonPress()
@@ -8,6 +8,7 @@ import com.reactnativestripesdk.utils.getLongOr
8
8
  import com.reactnativestripesdk.utils.getStringList
9
9
  import com.reactnativestripesdk.utils.isEmpty
10
10
  import com.stripe.android.paymentelement.PaymentMethodOptionsSetupFutureUsagePreview
11
+ import com.stripe.android.paymentsheet.CardFundingFilteringPrivatePreview
11
12
  import com.stripe.android.paymentsheet.PaymentSheet
12
13
 
13
14
  @Throws(PaymentSheetException::class)
@@ -226,3 +227,20 @@ internal fun mapToCardBrandCategory(brand: String): PaymentSheet.CardBrandAccept
226
227
  "discover" -> PaymentSheet.CardBrandAcceptance.BrandCategory.Discover
227
228
  else -> null
228
229
  }
230
+
231
+ @OptIn(CardFundingFilteringPrivatePreview::class)
232
+ internal fun mapToAllowedCardFundingTypes(params: ReadableMap?): List<PaymentSheet.CardFundingType>? {
233
+ val cardFundingFiltering = params?.getMap("cardFundingFiltering") ?: return null
234
+ val allowedTypes = cardFundingFiltering.getStringList("allowedCardFundingTypes") ?: return null
235
+
236
+ return allowedTypes
237
+ .mapNotNull { type ->
238
+ when (type) {
239
+ "debit" -> PaymentSheet.CardFundingType.Debit
240
+ "credit" -> PaymentSheet.CardFundingType.Credit
241
+ "prepaid" -> PaymentSheet.CardFundingType.Prepaid
242
+ "unknown" -> PaymentSheet.CardFundingType.Unknown
243
+ else -> null
244
+ }
245
+ }.ifEmpty { null }
246
+ }
@@ -45,8 +45,8 @@ import com.stripe.android.paymentelement.ConfirmCustomPaymentMethodCallback
45
45
  import com.stripe.android.paymentelement.CreateIntentWithConfirmationTokenCallback
46
46
  import com.stripe.android.paymentelement.CustomPaymentMethodResult
47
47
  import com.stripe.android.paymentelement.CustomPaymentMethodResultHandler
48
- import com.stripe.android.paymentelement.ExperimentalCustomPaymentMethodsApi
49
48
  import com.stripe.android.paymentelement.PaymentMethodOptionsSetupFutureUsagePreview
49
+ import com.stripe.android.paymentsheet.CardFundingFilteringPrivatePreview
50
50
  import com.stripe.android.paymentsheet.CreateIntentCallback
51
51
  import com.stripe.android.paymentsheet.CreateIntentResult
52
52
  import com.stripe.android.paymentsheet.PaymentOptionResultCallback
@@ -58,13 +58,16 @@ 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,
66
69
  ExperimentalAllowsRemovalOfLastSavedPaymentMethodApi::class,
67
- ExperimentalCustomPaymentMethodsApi::class,
70
+ CardFundingFilteringPrivatePreview::class,
68
71
  )
69
72
  class PaymentSheetManager(
70
73
  context: ReactApplicationContext,
@@ -85,7 +88,6 @@ class PaymentSheetManager(
85
88
  private var keepJsAwake: KeepJsAwakeTask? = null
86
89
 
87
90
  @SuppressLint("RestrictedApi")
88
- @OptIn(ExperimentalCustomPaymentMethodsApi::class)
89
91
  override fun onCreate() {
90
92
  val activity = getCurrentActivityOrResolveWithError(initPromise) ?: return
91
93
  val merchantDisplayName = arguments.getString("merchantDisplayName").orEmpty()
@@ -140,28 +142,42 @@ class PaymentSheetManager(
140
142
 
141
143
  val paymentOptionCallback =
142
144
  PaymentOptionResultCallback { paymentOptionResult ->
143
- val result =
144
- paymentOptionResult.paymentOption?.let {
145
- val bitmap = getBitmapFromDrawable(it.icon())
146
- val imageString = getBase64FromBitmap(bitmap)
145
+ paymentOptionResult.paymentOption?.let { paymentOption ->
146
+ // Convert drawable to bitmap asynchronously to avoid shared state issues
147
+ CoroutineScope(Dispatchers.Default).launch {
148
+ val imageString =
149
+ try {
150
+ convertDrawableToBase64(paymentOption.icon())
151
+ } catch (e: Exception) {
152
+ val result =
153
+ createError(
154
+ PaymentSheetErrorType.Failed.toString(),
155
+ "Failed to process payment option image: ${e.message}",
156
+ )
157
+ resolvePresentPromise(result)
158
+ return@launch
159
+ }
160
+
147
161
  val option: WritableMap = Arguments.createMap()
148
- option.putString("label", it.label)
162
+ option.putString("label", paymentOption.label)
149
163
  option.putString("image", imageString)
150
164
  val additionalFields: Map<String, Any> = mapOf("didCancel" to paymentOptionResult.didCancel)
151
- createResult("paymentOption", option, additionalFields)
165
+ val result = createResult("paymentOption", option, additionalFields)
166
+ resolvePresentPromise(result)
152
167
  }
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
- }
168
+ } ?: run {
169
+ val result =
170
+ if (paymentSheetTimedOut) {
171
+ paymentSheetTimedOut = false
172
+ createError(PaymentSheetErrorType.Timeout.toString(), "The payment has timed out")
173
+ } else {
174
+ createError(
175
+ PaymentSheetErrorType.Canceled.toString(),
176
+ "The payment option selection flow has been canceled",
177
+ )
163
178
  }
164
- resolvePresentPromise(result)
179
+ resolvePresentPromise(result)
180
+ }
165
181
  }
166
182
 
167
183
  val paymentResultCallback =
@@ -268,7 +284,9 @@ class PaymentSheetManager(
268
284
  mapToPreferredNetworks(arguments.getIntegerList("preferredNetworks")),
269
285
  ).allowsRemovalOfLastSavedPaymentMethod(allowsRemovalOfLastSavedPaymentMethod)
270
286
  .cardBrandAcceptance(mapToCardBrandAcceptance(arguments))
271
- .customPaymentMethods(parseCustomPaymentMethods(arguments.getMap("customPaymentMethodConfiguration")))
287
+ .apply {
288
+ mapToAllowedCardFundingTypes(arguments)?.let { allowedCardFundingTypes(it) }
289
+ }.customPaymentMethods(parseCustomPaymentMethods(arguments.getMap("customPaymentMethodConfiguration")))
272
290
 
273
291
  primaryButtonLabel?.let { configurationBuilder.primaryButtonLabel(it) }
274
292
  paymentMethodOrder?.let { configurationBuilder.paymentMethodOrder(it) }
@@ -413,16 +431,31 @@ class PaymentSheetManager(
413
431
  private fun configureFlowController() {
414
432
  val onFlowControllerConfigure =
415
433
  PaymentSheet.FlowController.ConfigCallback { _, _ ->
416
- val result =
417
- flowController?.getPaymentOption()?.let {
418
- val bitmap = getBitmapFromDrawable(it.icon())
419
- val imageString = getBase64FromBitmap(bitmap)
434
+ flowController?.getPaymentOption()?.let { paymentOption ->
435
+ // Launch async job to convert drawable, but resolve promise synchronously
436
+ CoroutineScope(Dispatchers.Default).launch {
437
+ val imageString =
438
+ try {
439
+ convertDrawableToBase64(paymentOption.icon())
440
+ } catch (e: Exception) {
441
+ val result =
442
+ createError(
443
+ PaymentSheetErrorType.Failed.toString(),
444
+ "Failed to process payment option image: ${e.message}",
445
+ )
446
+ initPromise.resolve(result)
447
+ return@launch
448
+ }
449
+
420
450
  val option: WritableMap = Arguments.createMap()
421
- option.putString("label", it.label)
451
+ option.putString("label", paymentOption.label)
422
452
  option.putString("image", imageString)
423
- createResult("paymentOption", option)
424
- } ?: run { Arguments.createMap() }
425
- initPromise.resolve(result)
453
+ val result = createResult("paymentOption", option)
454
+ initPromise.resolve(result)
455
+ }
456
+ } ?: run {
457
+ initPromise.resolve(Arguments.createMap())
458
+ }
426
459
  }
427
460
 
428
461
  if (!paymentIntentClientSecret.isNullOrEmpty()) {
@@ -466,7 +499,6 @@ class PaymentSheetManager(
466
499
  } ?: run { resolvePresentPromise(map) }
467
500
  }
468
501
 
469
- @OptIn(ExperimentalCustomPaymentMethodsApi::class)
470
502
  override fun onConfirmCustomPaymentMethod(
471
503
  customPaymentMethod: PaymentSheet.CustomPaymentMethod,
472
504
  billingDetails: PaymentMethod.BillingDetails,
@@ -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
 
@@ -69,6 +69,8 @@ abstract class StripeAbstractComposeView(
69
69
 
70
70
  private var innerComposeView: InnerComposeView? = null
71
71
  private var isLifecycleSetup = false
72
+ private var activityLifecycleOwner: LifecycleOwner? = null
73
+ private var activityLifecycleObserver: LifecycleEventObserver? = null
72
74
 
73
75
  // Create a lifecycle this is tied to the activity, but that we can manually
74
76
  // update to DESTROYED state when the view is dropped.
@@ -113,24 +115,34 @@ abstract class StripeAbstractComposeView(
113
115
  return
114
116
  }
115
117
 
116
- ((context as? ReactContext)?.currentActivity as? LifecycleOwner?)?.let {
118
+ ((context as? ReactContext)?.currentActivity as? LifecycleOwner?)?.let { owner ->
117
119
  isLifecycleSetup = true
120
+ activityLifecycleOwner = owner
118
121
 
119
122
  // Setup the lifecycle to match the activity.
120
- it.lifecycle.addObserver(
123
+ val observer =
121
124
  object : LifecycleEventObserver {
122
125
  override fun onStateChanged(
123
126
  source: LifecycleOwner,
124
127
  event: Lifecycle.Event,
125
128
  ) {
126
- lifecycleRegistry.handleLifecycleEvent(event)
129
+ if (lifecycleRegistry.currentState != Lifecycle.State.DESTROYED) {
130
+ lifecycleRegistry.handleLifecycleEvent(event)
131
+ }
127
132
  }
128
- },
129
- )
133
+ }
134
+ activityLifecycleObserver = observer
135
+ owner.lifecycle.addObserver(observer)
130
136
  }
131
137
  }
132
138
 
133
139
  fun handleOnDropViewInstance() {
140
+ activityLifecycleObserver?.let { observer ->
141
+ activityLifecycleOwner?.lifecycle?.removeObserver(observer)
142
+ }
143
+ activityLifecycleObserver = null
144
+ activityLifecycleOwner = null
145
+
134
146
  if (lifecycleRegistry.currentState.isAtLeast(Lifecycle.State.CREATED)) {
135
147
  lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
136
148
  }
@@ -1072,6 +1072,10 @@ class StripeSdkModule(
1072
1072
  promise.resolve(createMissingInitError())
1073
1073
  return
1074
1074
  }
1075
+
1076
+ // Use connectedAccountId from params if provided, otherwise fall back to global stripeAccountId
1077
+ val accountId = getValOr(params, "connectedAccountId", null) ?: stripeAccountId
1078
+
1075
1079
  unregisterStripeUIManager(financialConnectionsSheetManager)
1076
1080
  financialConnectionsSheetManager =
1077
1081
  FinancialConnectionsSheetManager(
@@ -1079,7 +1083,7 @@ class StripeSdkModule(
1079
1083
  clientSecret,
1080
1084
  FinancialConnectionsSheetManager.Mode.ForToken,
1081
1085
  publishableKey,
1082
- stripeAccountId,
1086
+ accountId,
1083
1087
  ).also {
1084
1088
  registerStripeUIManager(it)
1085
1089
  it.present(promise)
@@ -1097,6 +1101,9 @@ class StripeSdkModule(
1097
1101
  return
1098
1102
  }
1099
1103
 
1104
+ // Use connectedAccountId from params if provided, otherwise fall back to global stripeAccountId
1105
+ val accountId = getValOr(params, "connectedAccountId", null) ?: stripeAccountId
1106
+
1100
1107
  unregisterStripeUIManager(financialConnectionsSheetManager)
1101
1108
  financialConnectionsSheetManager =
1102
1109
  FinancialConnectionsSheetManager(
@@ -1104,7 +1111,7 @@ class StripeSdkModule(
1104
1111
  clientSecret,
1105
1112
  FinancialConnectionsSheetManager.Mode.ForSession,
1106
1113
  publishableKey,
1107
- stripeAccountId,
1114
+ accountId,
1108
1115
  ).also {
1109
1116
  registerStripeUIManager(it)
1110
1117
  it.present(promise)
@@ -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) {
@@ -25,7 +25,6 @@ import com.stripe.android.model.StripeIntent
25
25
  import com.stripe.android.model.StripeIntent.NextActionData
26
26
  import com.stripe.android.model.StripeIntent.NextActionType
27
27
  import com.stripe.android.model.Token
28
- import com.stripe.android.paymentelement.ExperimentalCustomPaymentMethodsApi
29
28
  import com.stripe.android.paymentsheet.PaymentSheet
30
29
  import java.lang.IllegalArgumentException
31
30
 
@@ -1036,7 +1035,6 @@ private fun Map<String, Any?>.toReadableMap(): ReadableMap {
1036
1035
  return writableMap
1037
1036
  }
1038
1037
 
1039
- @OptIn(ExperimentalCustomPaymentMethodsApi::class)
1040
1038
  @SuppressLint("RestrictedApi")
1041
1039
  internal fun parseCustomPaymentMethods(customPaymentMethodConfig: ReadableMap?): List<PaymentSheet.CustomPaymentMethod> {
1042
1040
  if (customPaymentMethodConfig == null) {