@stripe/stripe-react-native 0.48.0 → 0.50.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 (139) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +1 -1
  3. package/android/gradle.properties +1 -1
  4. package/android/src/main/AndroidManifest.xml +10 -0
  5. package/android/src/main/java/com/reactnativestripesdk/CustomPaymentMethodActivity.kt +81 -0
  6. package/android/src/main/java/com/reactnativestripesdk/EmbeddedPaymentElementView.kt +171 -56
  7. package/android/src/main/java/com/reactnativestripesdk/EmbeddedPaymentElementViewManager.kt +34 -3
  8. package/android/src/main/java/com/reactnativestripesdk/PaymentMethodCreateParamsFactory.kt +0 -17
  9. package/android/src/main/java/com/reactnativestripesdk/PaymentOptionDisplayDataMapper.kt +0 -2
  10. package/android/src/main/java/com/reactnativestripesdk/PaymentSheetAppearance.kt +57 -3
  11. package/android/src/main/java/com/reactnativestripesdk/PaymentSheetFragment.kt +140 -19
  12. package/android/src/main/java/com/reactnativestripesdk/StripeSdkModule.kt +22 -0
  13. package/android/src/main/java/com/reactnativestripesdk/utils/Mappers.kt +52 -6
  14. package/android/src/main/res/values/styles.xml +27 -0
  15. package/android/src/oldarch/java/com/reactnativestripesdk/NativeStripeSdkModuleSpec.java +8 -0
  16. package/ios/ApplePayButtonManager.m +4 -0
  17. package/ios/ApplePayButtonView.swift +11 -4
  18. package/ios/Mappers.swift +0 -5
  19. package/ios/NewArch/ApplePayButtonComponentView.mm +6 -0
  20. package/ios/OldArch/StripeSdkEventEmitterCompat.h +1 -1
  21. package/ios/OldArch/StripeSdkEventEmitterCompat.m +7 -1
  22. package/ios/PaymentMethodFactory.swift +0 -17
  23. package/ios/PaymentSheetAppearance.swift +22 -1
  24. package/ios/StripeSdk.mm +7 -0
  25. package/ios/StripeSdkEmitter.swift +1 -0
  26. package/ios/StripeSdkImpl+Embedded.swift +39 -17
  27. package/ios/StripeSdkImpl+PaymentSheet.swift +156 -5
  28. package/ios/StripeSdkImpl.swift +43 -10
  29. package/lib/commonjs/components/AddToWalletButton.js +1 -1
  30. package/lib/commonjs/components/AddToWalletButton.js.map +1 -1
  31. package/lib/commonjs/components/AddressSheet.js +1 -1
  32. package/lib/commonjs/components/AddressSheet.js.map +1 -1
  33. package/lib/commonjs/components/AuBECSDebitForm.js +1 -1
  34. package/lib/commonjs/components/AuBECSDebitForm.js.map +1 -1
  35. package/lib/commonjs/components/CardField.js +1 -1
  36. package/lib/commonjs/components/CardField.js.map +1 -1
  37. package/lib/commonjs/components/CardForm.js +1 -1
  38. package/lib/commonjs/components/CardForm.js.map +1 -1
  39. package/lib/commonjs/components/CustomerSheet.js +1 -1
  40. package/lib/commonjs/components/CustomerSheet.js.map +1 -1
  41. package/lib/commonjs/components/PlatformPayButton.js +1 -1
  42. package/lib/commonjs/components/PlatformPayButton.js.map +1 -1
  43. package/lib/commonjs/components/StripeContainer.js +1 -1
  44. package/lib/commonjs/components/StripeContainer.js.map +1 -1
  45. package/lib/commonjs/events.js.map +1 -1
  46. package/lib/commonjs/functions.js +1 -1
  47. package/lib/commonjs/functions.js.map +1 -1
  48. package/lib/commonjs/hooks/useStripe.js +1 -1
  49. package/lib/commonjs/hooks/useStripe.js.map +1 -1
  50. package/lib/commonjs/specs/NativeApplePayButton.js +1 -1
  51. package/lib/commonjs/specs/NativeApplePayButton.js.map +1 -1
  52. package/lib/commonjs/specs/NativeStripeSdkModule.js.map +1 -1
  53. package/lib/commonjs/types/EmbeddedPaymentElement.js +1 -1
  54. package/lib/commonjs/types/EmbeddedPaymentElement.js.map +1 -1
  55. package/lib/commonjs/types/PaymentIntent.js.map +1 -1
  56. package/lib/commonjs/types/PaymentSheet.js +1 -1
  57. package/lib/commonjs/types/PaymentSheet.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/CustomerSheet.js +1 -1
  69. package/lib/module/components/CustomerSheet.js.map +1 -1
  70. package/lib/module/components/PlatformPayButton.js +1 -1
  71. package/lib/module/components/PlatformPayButton.js.map +1 -1
  72. package/lib/module/components/StripeContainer.js +1 -1
  73. package/lib/module/components/StripeContainer.js.map +1 -1
  74. package/lib/module/events.js.map +1 -1
  75. package/lib/module/functions.js +1 -1
  76. package/lib/module/functions.js.map +1 -1
  77. package/lib/module/hooks/useStripe.js +1 -1
  78. package/lib/module/hooks/useStripe.js.map +1 -1
  79. package/lib/module/specs/NativeApplePayButton.js +1 -1
  80. package/lib/module/specs/NativeApplePayButton.js.map +1 -1
  81. package/lib/module/specs/NativeStripeSdkModule.js.map +1 -1
  82. package/lib/module/types/EmbeddedPaymentElement.js +1 -1
  83. package/lib/module/types/EmbeddedPaymentElement.js.map +1 -1
  84. package/lib/module/types/PaymentIntent.js.map +1 -1
  85. package/lib/module/types/PaymentSheet.js +1 -1
  86. package/lib/module/types/PaymentSheet.js.map +1 -1
  87. package/lib/typescript/src/components/PlatformPayButton.d.ts.map +1 -1
  88. package/lib/typescript/src/events.d.ts +1 -1
  89. package/lib/typescript/src/events.d.ts.map +1 -1
  90. package/lib/typescript/src/functions.d.ts.map +1 -1
  91. package/lib/typescript/src/specs/NativeApplePayButton.d.ts +4 -0
  92. package/lib/typescript/src/specs/NativeApplePayButton.d.ts.map +1 -1
  93. package/lib/typescript/src/specs/NativeStripeSdkModule.d.ts +2 -0
  94. package/lib/typescript/src/specs/NativeStripeSdkModule.d.ts.map +1 -1
  95. package/lib/typescript/src/types/EmbeddedPaymentElement.d.ts +29 -0
  96. package/lib/typescript/src/types/EmbeddedPaymentElement.d.ts.map +1 -1
  97. package/lib/typescript/src/types/PaymentIntent.d.ts +2 -11
  98. package/lib/typescript/src/types/PaymentIntent.d.ts.map +1 -1
  99. package/lib/typescript/src/types/PaymentMethod.d.ts +2 -13
  100. package/lib/typescript/src/types/PaymentMethod.d.ts.map +1 -1
  101. package/lib/typescript/src/types/PaymentSheet.d.ts +78 -2
  102. package/lib/typescript/src/types/PaymentSheet.d.ts.map +1 -1
  103. package/package-lock.json +14114 -0
  104. package/package.json +1 -1
  105. package/patches/README.md +55 -0
  106. package/patches/old-arch-codegen-fix.patch +87 -0
  107. package/src/components/PlatformPayButton.tsx +12 -4
  108. package/src/events.ts +3 -1
  109. package/src/functions.ts +36 -1
  110. package/src/specs/NativeApplePayButton.ts +5 -0
  111. package/src/specs/NativeStripeSdkModule.ts +4 -0
  112. package/src/types/EmbeddedPaymentElement.tsx +80 -2
  113. package/src/types/PaymentIntent.ts +1 -11
  114. package/src/types/PaymentMethod.ts +0 -14
  115. package/src/types/PaymentSheet.ts +86 -1
  116. package/stripe-react-native.podspec +1 -1
  117. package/android/.gradle/8.11.1/checksums/checksums.lock +0 -0
  118. package/android/.gradle/8.11.1/checksums/md5-checksums.bin +0 -0
  119. package/android/.gradle/8.11.1/checksums/sha1-checksums.bin +0 -0
  120. package/android/.gradle/8.11.1/executionHistory/executionHistory.bin +0 -0
  121. package/android/.gradle/8.11.1/executionHistory/executionHistory.lock +0 -0
  122. package/android/.gradle/8.11.1/fileChanges/last-build.bin +0 -0
  123. package/android/.gradle/8.11.1/fileHashes/fileHashes.bin +0 -0
  124. package/android/.gradle/8.11.1/fileHashes/fileHashes.lock +0 -0
  125. package/android/.gradle/8.11.1/gc.properties +0 -0
  126. package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
  127. package/android/.gradle/buildOutputCleanup/cache.properties +0 -2
  128. package/android/.gradle/buildOutputCleanup/outputFiles.bin +0 -0
  129. package/android/.gradle/config.properties +0 -2
  130. package/android/.gradle/file-system.probe +0 -0
  131. package/android/.gradle/vcs-1/gc.properties +0 -0
  132. package/android/.idea/caches/deviceStreaming.xml +0 -619
  133. package/android/.idea/compiler.xml +0 -6
  134. package/android/.idea/gradle.xml +0 -19
  135. package/android/.idea/migrations.xml +0 -10
  136. package/android/.idea/misc.xml +0 -10
  137. package/android/.idea/runConfigurations.xml +0 -17
  138. package/android/.idea/vcs.xml +0 -6
  139. package/android/local.properties +0 -8
package/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 0.50.0 - 2025-07-17
4
+
5
+ **Features**
6
+ - Added support for Custom Payment Methods in PaymentSheet and Embedded Payment Element.
7
+
8
+ **Fixes**
9
+ - Removed Sofort from playground pages. Sofort is no longer support by Stripe.
10
+ - **Patches**
11
+ - Fixed codegen error when using React Native 0.74+ with old architecture by converting EventEmitter properties to callback functions in TurboModule interface. [#1977](https://github.com/stripe/stripe-react-native/issues/1977). See `patches/README.md` for more info.
12
+
13
+ ## 0.49.0 - 2025-07-02
14
+
15
+ **Features**
16
+ - Added rowSelectionBehavior to `EmbeddedPaymentElementConfiguration` with `immediateAction` option
17
+ - Added `flatWithChevron` to `AppearanceParams.embeddedPaymentElement.rowConfig.style`
18
+ - Added `PaymentMethodOptions` to `PaymentMode` to enable setting payment method level setup future usage value
19
+ - Added `None` to `FutureUsage`
20
+
3
21
  ## 0.48.0 - 2025-06-11
4
22
 
5
23
  **Feature**
package/README.md CHANGED
@@ -23,7 +23,7 @@ Get started with our [📚 integration guides](https://stripe.com/docs/payments/
23
23
 
24
24
  **Native UI**: We provide native screens and elements to securely collect payment details on Android and iOS.
25
25
 
26
- **PaymentSheet**: [Learn how to integrate](https://stripe.com/docs/payments/accept-a-payment) PaymentSheet, our new pre-built payments UI for mobile apps. PaymentSheet lets you accept cards, Apple Pay, Google Pay, and much more out of the box and also supports saving & reusing payment methods. PaymentSheet currently accepts the following payment methods: Card, Apple Pay, Google Pay, SEPA Debit, Bancontact, iDEAL, EPS, P24, Afterpay/Clearpay, Klarna, Giropay, Sofort, and ACH.
26
+ **PaymentSheet**: [Learn how to integrate](https://stripe.com/docs/payments/accept-a-payment) PaymentSheet, our new pre-built payments UI for mobile apps. PaymentSheet lets you accept cards, Apple Pay, Google Pay, and much more out of the box and also supports saving & reusing payment methods. PaymentSheet currently accepts the following payment methods: Card, Apple Pay, Google Pay, SEPA Debit, Bancontact, iDEAL, EPS, P24, Afterpay/Clearpay, Klarna, Giropay, and ACH.
27
27
 
28
28
  #### Recommended usage
29
29
 
@@ -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=21.17.+
6
+ StripeSdk_stripeVersion=21.19.+
@@ -1,3 +1,13 @@
1
1
  <manifest xmlns:android="http://schemas.android.com/apk/res/android"
2
2
  package="com.reactnativestripesdk">
3
+
4
+ <application>
5
+ <activity
6
+ android:name=".CustomPaymentMethodActivity"
7
+ android:theme="@style/Theme.StripeReactNative.Transparent"
8
+ android:exported="false"
9
+ android:launchMode="singleTop"
10
+ android:excludeFromRecents="true"
11
+ android:noHistory="true" />
12
+ </application>
3
13
  </manifest>
@@ -0,0 +1,81 @@
1
+ package com.reactnativestripesdk
2
+
3
+ import android.os.Bundle
4
+ import android.view.MotionEvent
5
+ import com.facebook.react.ReactActivity
6
+ import java.lang.ref.WeakReference
7
+
8
+ /**
9
+ * A transparent activity that is launched when the Payment Element requests the
10
+ * `confirmCustomPaymentMethodCallback`.
11
+ *
12
+ * Its only purpose is to bring the app back to the foreground (the Stripe
13
+ * SDK launches its own proxy activity which pauses the host React Native
14
+ * activity). Having a React (transparent) activity on top ensures that React
15
+ * Native can display UI elements such as `Alert` dialogs coming from
16
+ * JavaScript.
17
+ *
18
+ * The activity uses a translucent theme to minimize visibility and is excluded
19
+ * from recents, though it may still be briefly visible to the end-user during
20
+ * certain operations.
21
+ */
22
+ class CustomPaymentMethodActivity : ReactActivity() {
23
+ override fun onCreate(savedInstanceState: Bundle?) {
24
+ // Disable the transition animation to make it truly invisible
25
+ overridePendingTransition(0, 0)
26
+ super.onCreate(savedInstanceState)
27
+ }
28
+
29
+ override fun getMainComponentName(): String? {
30
+ // We don't want to mount another React Native root – returning null is
31
+ // enough to make ReactActivity skip loading a JS component while still
32
+ // hooking into the lifecycle so that ReactContext is aware of this
33
+ // activity.
34
+ return null
35
+ }
36
+
37
+ override fun onTouchEvent(event: MotionEvent?): Boolean {
38
+ // Ensure touch events are properly handled by React Native
39
+ return super.onTouchEvent(event)
40
+ }
41
+
42
+ override fun dispatchTouchEvent(event: MotionEvent?): Boolean {
43
+ // Ensure touch events are properly dispatched to React Native
44
+ return super.dispatchTouchEvent(event)
45
+ }
46
+
47
+ override fun onResume() {
48
+ super.onResume()
49
+ // Ensure the activity is properly focused for touch events
50
+ currentFocus?.requestFocus()
51
+ }
52
+
53
+ override fun finish() {
54
+ super.finish()
55
+ // Disable the exit animation as well
56
+ overridePendingTransition(0, 0)
57
+
58
+ // Clear the weak reference when finished
59
+ if (currentActivityRef?.get() == this) {
60
+ currentActivityRef = null
61
+ }
62
+ }
63
+
64
+ companion object {
65
+ @Volatile
66
+ private var currentActivityRef: WeakReference<CustomPaymentMethodActivity>? = null
67
+
68
+ fun finishCurrent() {
69
+ currentActivityRef?.get()?.let { activity ->
70
+ activity.runOnUiThread {
71
+ activity.finish()
72
+ }
73
+ }
74
+ }
75
+ }
76
+
77
+ override fun onStart() {
78
+ super.onStart()
79
+ currentActivityRef = WeakReference(this)
80
+ }
81
+ }
@@ -1,13 +1,17 @@
1
1
  package com.reactnativestripesdk
2
2
 
3
3
  import android.content.Context
4
+ import android.content.Intent
5
+ import android.util.Log
4
6
  import androidx.compose.foundation.layout.Box
5
7
  import androidx.compose.foundation.layout.requiredHeight
6
8
  import androidx.compose.runtime.Composable
7
9
  import androidx.compose.runtime.LaunchedEffect
8
10
  import androidx.compose.runtime.getValue
9
11
  import androidx.compose.runtime.mutableIntStateOf
12
+ import androidx.compose.runtime.mutableStateOf
10
13
  import androidx.compose.runtime.remember
14
+ import androidx.compose.runtime.rememberCoroutineScope
11
15
  import androidx.compose.runtime.setValue
12
16
  import androidx.compose.ui.Modifier
13
17
  import androidx.compose.ui.layout.layout
@@ -18,18 +22,29 @@ import androidx.compose.ui.unit.dp
18
22
  import com.facebook.react.bridge.Arguments
19
23
  import com.facebook.react.uimanager.ThemedReactContext
20
24
  import com.reactnativestripesdk.utils.KeepJsAwakeTask
25
+ import com.reactnativestripesdk.utils.mapFromCustomPaymentMethod
21
26
  import com.reactnativestripesdk.utils.mapFromPaymentMethod
27
+ import com.stripe.android.model.PaymentMethod
28
+ import com.stripe.android.paymentelement.CustomPaymentMethodResult
29
+ import com.stripe.android.paymentelement.CustomPaymentMethodResultHandler
22
30
  import com.stripe.android.paymentelement.EmbeddedPaymentElement
23
- import com.stripe.android.paymentelement.ExperimentalEmbeddedPaymentElementApi
31
+ import com.stripe.android.paymentelement.ExperimentalCustomPaymentMethodsApi
24
32
  import com.stripe.android.paymentelement.rememberEmbeddedPaymentElement
25
33
  import com.stripe.android.paymentsheet.CreateIntentResult
26
34
  import com.stripe.android.paymentsheet.PaymentSheet
27
35
  import kotlinx.coroutines.CompletableDeferred
28
36
  import kotlinx.coroutines.channels.Channel
37
+ import kotlinx.coroutines.delay
29
38
  import kotlinx.coroutines.flow.consumeAsFlow
39
+ import kotlinx.coroutines.launch
30
40
  import toWritableMap
31
41
 
32
- @OptIn(ExperimentalEmbeddedPaymentElementApi::class)
42
+ enum class RowSelectionBehaviorType {
43
+ Default,
44
+ ImmediateAction,
45
+ }
46
+
47
+ @OptIn(ExperimentalCustomPaymentMethodsApi::class)
33
48
  class EmbeddedPaymentElementView(
34
49
  context: Context,
35
50
  ) : StripeAbstractComposeView(context) {
@@ -47,75 +62,175 @@ class EmbeddedPaymentElementView(
47
62
  var latestIntentConfig: PaymentSheet.IntentConfiguration? = null
48
63
  var latestElementConfig: EmbeddedPaymentElement.Configuration? = null
49
64
 
65
+ val rowSelectionBehaviorType = mutableStateOf<RowSelectionBehaviorType?>(null)
66
+
50
67
  private val reactContext get() = context as ThemedReactContext
51
68
  private val events = Channel<Event>(Channel.UNLIMITED)
52
69
 
53
- private val builder by lazy {
54
- EmbeddedPaymentElement.Builder(
55
- createIntentCallback = { paymentMethod, shouldSavePaymentMethod ->
56
- val stripeSdkModule =
70
+ @OptIn(ExperimentalCustomPaymentMethodsApi::class)
71
+ @Composable
72
+ override fun Content() {
73
+ val type by remember { rowSelectionBehaviorType }
74
+ val coroutineScope = rememberCoroutineScope()
75
+
76
+ val confirmCustomPaymentMethodCallback =
77
+ remember(coroutineScope) {
78
+ {
79
+ customPaymentMethod: PaymentSheet.CustomPaymentMethod,
80
+ billingDetails: PaymentMethod.BillingDetails,
81
+ ->
82
+ // Launch a transparent Activity to ensure React Native UI can appear on top of the Stripe proxy activity.
57
83
  try {
58
- requireStripeSdkModule()
59
- } catch (ex: IllegalArgumentException) {
60
- return@Builder CreateIntentResult.Failure(
61
- cause =
62
- Exception(
63
- "Tried to call confirmHandler, but no callback was found. Please file an issue: https://github.com/stripe/stripe-react-native/issues",
64
- ),
65
- displayMessage = "An unexpected error occurred",
66
- )
84
+ val intent =
85
+ Intent(reactContext, CustomPaymentMethodActivity::class.java).apply {
86
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
87
+ addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)
88
+ }
89
+ reactContext.startActivity(intent)
90
+ } catch (e: Exception) {
91
+ Log.e("StripeReactNative", "Failed to start CustomPaymentMethodActivity", e)
67
92
  }
68
93
 
69
- // Make sure that JS is active since the activity will be paused when stripe ui is presented.
70
- val keepJsAwakeTask = KeepJsAwakeTask(reactContext.reactApplicationContext).apply { start() }
94
+ val stripeSdkModule =
95
+ try {
96
+ requireStripeSdkModule()
97
+ } catch (ex: IllegalArgumentException) {
98
+ Log.e("StripeReactNative", "StripeSdkModule not found for CPM callback", ex)
99
+ CustomPaymentMethodActivity.finishCurrent()
100
+ return@remember
101
+ }
71
102
 
72
- val params =
73
- Arguments.createMap().apply {
74
- putMap("paymentMethod", mapFromPaymentMethod(paymentMethod))
75
- putBoolean("shouldSavePaymentMethod", shouldSavePaymentMethod)
76
- }
103
+ // Keep JS awake while React Native is backgrounded by Stripe SDK.
104
+ val keepJsAwakeTask =
105
+ KeepJsAwakeTask(reactContext.reactApplicationContext).apply { start() }
77
106
 
78
- stripeSdkModule.emitOnConfirmHandlerCallback(params)
107
+ // Run on coroutine scope.
108
+ coroutineScope.launch {
109
+ try {
110
+ // Give the CustomPaymentMethodActivity a moment to fully initialize
111
+ delay(100)
79
112
 
80
- val resultFromJavascript = stripeSdkModule.embeddedIntentCreationCallback.await()
81
- // reset the completable
82
- stripeSdkModule.embeddedIntentCreationCallback = CompletableDeferred()
113
+ // Emit event so JS can show the Alert and eventually respond via `customPaymentMethodResultCallback`.
114
+ stripeSdkModule.emitOnCustomPaymentMethodConfirmHandlerCallback(
115
+ mapFromCustomPaymentMethod(customPaymentMethod, billingDetails),
116
+ )
83
117
 
84
- keepJsAwakeTask.stop()
118
+ // Await JS result.
119
+ val resultFromJs = stripeSdkModule.customPaymentMethodResultCallback.await()
85
120
 
86
- resultFromJavascript.getString("clientSecret")?.let {
87
- CreateIntentResult.Success(clientSecret = it)
88
- } ?: run {
89
- val errorMap = resultFromJavascript.getMap("error")
90
- CreateIntentResult.Failure(
91
- cause = Exception(errorMap?.getString("message")),
92
- displayMessage = errorMap?.getString("localizedMessage"),
93
- )
121
+ keepJsAwakeTask.stop()
122
+
123
+ val status = resultFromJs.getString("status")
124
+
125
+ val nativeResult =
126
+ when (status) {
127
+ "completed" ->
128
+ CustomPaymentMethodResult
129
+ .completed()
130
+ "canceled" ->
131
+ CustomPaymentMethodResult
132
+ .canceled()
133
+ "failed" -> {
134
+ val errMsg = resultFromJs.getString("error") ?: "Custom payment failed"
135
+ CustomPaymentMethodResult
136
+ .failed(displayMessage = errMsg)
137
+ }
138
+ else ->
139
+ CustomPaymentMethodResult
140
+ .failed(displayMessage = "Unknown status")
141
+ }
142
+
143
+ // Return result to Stripe SDK.
144
+ CustomPaymentMethodResultHandler.handleCustomPaymentMethodResult(
145
+ reactContext,
146
+ nativeResult,
147
+ )
148
+ } finally {
149
+ // Clean up the transparent activity
150
+ CustomPaymentMethodActivity.finishCurrent()
151
+ }
152
+ }
94
153
  }
95
- },
96
- resultCallback = { result ->
97
- val map =
98
- Arguments.createMap().apply {
99
- when (result) {
100
- is EmbeddedPaymentElement.Result.Completed -> {
101
- putString("status", "completed")
102
- }
103
- is EmbeddedPaymentElement.Result.Canceled -> {
104
- putString("status", "canceled")
154
+ }
155
+
156
+ val builder =
157
+ remember(type) {
158
+ EmbeddedPaymentElement
159
+ .Builder(
160
+ createIntentCallback = { paymentMethod, shouldSavePaymentMethod ->
161
+ val stripeSdkModule =
162
+ try {
163
+ requireStripeSdkModule()
164
+ } catch (ex: IllegalArgumentException) {
165
+ return@Builder CreateIntentResult.Failure(
166
+ cause =
167
+ Exception(
168
+ "Tried to call confirmHandler, but no callback was found. Please file an issue: https://github.com/stripe/stripe-react-native/issues",
169
+ ),
170
+ displayMessage = "An unexpected error occurred",
171
+ )
172
+ }
173
+
174
+ // Make sure that JS is active since the activity will be paused when stripe ui is presented.
175
+ val keepJsAwakeTask =
176
+ KeepJsAwakeTask(reactContext.reactApplicationContext).apply { start() }
177
+
178
+ val params =
179
+ Arguments.createMap().apply {
180
+ putMap("paymentMethod", mapFromPaymentMethod(paymentMethod))
181
+ putBoolean("shouldSavePaymentMethod", shouldSavePaymentMethod)
182
+ }
183
+
184
+ stripeSdkModule.emitOnConfirmHandlerCallback(params)
185
+
186
+ val resultFromJavascript = stripeSdkModule.embeddedIntentCreationCallback.await()
187
+ // reset the completable
188
+ stripeSdkModule.embeddedIntentCreationCallback = CompletableDeferred()
189
+
190
+ keepJsAwakeTask.stop()
191
+
192
+ resultFromJavascript.getString("clientSecret")?.let {
193
+ CreateIntentResult.Success(clientSecret = it)
194
+ } ?: run {
195
+ val errorMap = resultFromJavascript.getMap("error")
196
+ CreateIntentResult.Failure(
197
+ cause = Exception(errorMap?.getString("message")),
198
+ displayMessage = errorMap?.getString("localizedMessage"),
199
+ )
105
200
  }
106
- is EmbeddedPaymentElement.Result.Failed -> {
107
- putString("status", "failed")
108
- putString("error", result.error.message ?: "Unknown error")
201
+ },
202
+ resultCallback = { result ->
203
+ val map =
204
+ Arguments.createMap().apply {
205
+ when (result) {
206
+ is EmbeddedPaymentElement.Result.Completed -> {
207
+ putString("status", "completed")
208
+ }
209
+
210
+ is EmbeddedPaymentElement.Result.Canceled -> {
211
+ putString("status", "canceled")
212
+ }
213
+
214
+ is EmbeddedPaymentElement.Result.Failed -> {
215
+ putString("status", "failed")
216
+ putString("error", result.error.message ?: "Unknown error")
217
+ }
218
+ }
219
+ }
220
+ requireStripeSdkModule().emitEmbeddedPaymentElementFormSheetConfirmComplete(map)
221
+ },
222
+ ).confirmCustomPaymentMethodCallback(confirmCustomPaymentMethodCallback)
223
+ .rowSelectionBehavior(
224
+ if (type == RowSelectionBehaviorType.Default) {
225
+ EmbeddedPaymentElement.RowSelectionBehavior.default()
226
+ } else {
227
+ EmbeddedPaymentElement.RowSelectionBehavior.immediateAction {
228
+ requireStripeSdkModule().emitEmbeddedPaymentElementRowSelectionImmediateAction()
109
229
  }
110
- }
111
- }
112
- requireStripeSdkModule().emitEmbeddedPaymentElementFormSheetConfirmComplete(map)
113
- },
114
- )
115
- }
230
+ },
231
+ )
232
+ }
116
233
 
117
- @Composable
118
- override fun Content() {
119
234
  val embedded = rememberEmbeddedPaymentElement(builder)
120
235
  var height by remember {
121
236
  mutableIntStateOf(0)
@@ -18,13 +18,13 @@ import com.reactnativestripesdk.addresssheet.AddressSheetView
18
18
  import com.reactnativestripesdk.utils.PaymentSheetAppearanceException
19
19
  import com.reactnativestripesdk.utils.PaymentSheetException
20
20
  import com.reactnativestripesdk.utils.mapToPreferredNetworks
21
+ import com.reactnativestripesdk.utils.parseCustomPaymentMethods
21
22
  import com.reactnativestripesdk.utils.toBundleObject
22
23
  import com.stripe.android.ExperimentalAllowsRemovalOfLastSavedPaymentMethodApi
23
24
  import com.stripe.android.paymentelement.EmbeddedPaymentElement
24
- import com.stripe.android.paymentelement.ExperimentalEmbeddedPaymentElementApi
25
+ import com.stripe.android.paymentelement.ExperimentalCustomPaymentMethodsApi
25
26
  import com.stripe.android.paymentsheet.PaymentSheet
26
27
 
27
- @OptIn(ExperimentalEmbeddedPaymentElementApi::class)
28
28
  @ReactModule(name = EmbeddedPaymentElementViewManager.NAME)
29
29
  class EmbeddedPaymentElementViewManager :
30
30
  ViewGroupManager<EmbeddedPaymentElementView>(),
@@ -54,6 +54,9 @@ class EmbeddedPaymentElementViewManager :
54
54
  view: EmbeddedPaymentElementView,
55
55
  cfg: Dynamic,
56
56
  ) {
57
+ val rowSelectionBehaviorType = parseRowSelectionBehavior(cfg.asMap())
58
+ view.rowSelectionBehaviorType.value = rowSelectionBehaviorType
59
+
57
60
  val elementConfig = parseElementConfiguration(cfg.asMap(), view.context)
58
61
  view.latestElementConfig = elementConfig
59
62
  // if intentConfig is already set, configure immediately:
@@ -79,7 +82,10 @@ class EmbeddedPaymentElementViewManager :
79
82
  }
80
83
 
81
84
  @SuppressLint("RestrictedApi")
82
- @OptIn(ExperimentalAllowsRemovalOfLastSavedPaymentMethodApi::class)
85
+ @OptIn(
86
+ ExperimentalAllowsRemovalOfLastSavedPaymentMethodApi::class,
87
+ ExperimentalCustomPaymentMethodsApi::class,
88
+ )
83
89
  private fun parseElementConfiguration(
84
90
  map: ReadableMap,
85
91
  context: Context,
@@ -183,6 +189,16 @@ class EmbeddedPaymentElementViewManager :
183
189
  ),
184
190
  ).allowsRemovalOfLastSavedPaymentMethod(allowsRemovalOfLastSavedPaymentMethod)
185
191
  .cardBrandAcceptance(mapToCardBrandAcceptance(toBundleObject(map)))
192
+ // Serialize original ReadableMap because toBundleObject cannot keep arrays of objects
193
+ .customPaymentMethods(
194
+ parseCustomPaymentMethods(
195
+ toBundleObject(map.getMap("customPaymentMethodConfiguration")).apply {
196
+ map.getMap("customPaymentMethodConfiguration")?.let { readable ->
197
+ putSerializable("customPaymentMethodConfigurationReadableMap", readable.toHashMap())
198
+ }
199
+ },
200
+ ),
201
+ )
186
202
 
187
203
  primaryButtonLabel?.let { configurationBuilder.primaryButtonLabel(it) }
188
204
  paymentMethodOrder?.let { configurationBuilder.paymentMethodOrder(it) }
@@ -190,6 +206,21 @@ class EmbeddedPaymentElementViewManager :
190
206
  return configurationBuilder.build()
191
207
  }
192
208
 
209
+ private fun parseRowSelectionBehavior(map: ReadableMap): RowSelectionBehaviorType {
210
+ val rowSelectionBehavior =
211
+ map
212
+ .getMap("rowSelectionBehavior")
213
+ ?.getString("type")
214
+ ?.let { type ->
215
+ when (type) {
216
+ "immediateAction" -> RowSelectionBehaviorType.ImmediateAction
217
+ else -> RowSelectionBehaviorType.Default
218
+ }
219
+ }
220
+ ?: RowSelectionBehaviorType.Default
221
+ return rowSelectionBehavior
222
+ }
223
+
193
224
  private fun parseIntentConfiguration(map: ReadableMap): PaymentSheet.IntentConfiguration {
194
225
  val intentConfig = PaymentSheetFragment.buildIntentConfiguration(toBundleObject(map))
195
226
  return intentConfig ?: throw IllegalArgumentException("IntentConfiguration is null")
@@ -38,7 +38,6 @@ class PaymentMethodCreateParamsFactory(
38
38
  PaymentMethod.Type.Card -> createCardPaymentMethodParams()
39
39
  PaymentMethod.Type.Ideal -> createIDEALParams()
40
40
  PaymentMethod.Type.Alipay -> createAlipayParams()
41
- PaymentMethod.Type.Sofort -> createSofortParams()
42
41
  PaymentMethod.Type.Bancontact -> createBancontactParams()
43
42
  PaymentMethod.Type.SepaDebit -> createSepaParams()
44
43
  PaymentMethod.Type.Oxxo -> createOXXOParams()
@@ -79,21 +78,6 @@ class PaymentMethodCreateParamsFactory(
79
78
  @Throws(PaymentMethodCreateParamsException::class)
80
79
  private fun createAlipayParams(): PaymentMethodCreateParams = PaymentMethodCreateParams.createAlipay()
81
80
 
82
- @Throws(PaymentMethodCreateParamsException::class)
83
- private fun createSofortParams(): PaymentMethodCreateParams {
84
- val country =
85
- getValOr(paymentMethodData, "country", null)
86
- ?: run {
87
- throw PaymentMethodCreateParamsException("You must provide bank account country")
88
- }
89
-
90
- return PaymentMethodCreateParams.create(
91
- PaymentMethodCreateParams.Sofort(country = country),
92
- billingDetailsParams,
93
- metadata = metadataParams,
94
- )
95
- }
96
-
97
81
  @Throws(PaymentMethodCreateParamsException::class)
98
82
  private fun createBancontactParams(): PaymentMethodCreateParams {
99
83
  billingDetailsParams?.let {
@@ -271,7 +255,6 @@ class PaymentMethodCreateParamsFactory(
271
255
  PaymentMethod.Type.Affirm -> createAffirmStripeIntentParams(clientSecret, isPaymentIntent)
272
256
  PaymentMethod.Type.Ideal,
273
257
  PaymentMethod.Type.Alipay,
274
- PaymentMethod.Type.Sofort,
275
258
  PaymentMethod.Type.Bancontact,
276
259
  PaymentMethod.Type.SepaDebit,
277
260
  PaymentMethod.Type.Oxxo,
@@ -2,13 +2,11 @@ import com.facebook.react.bridge.Arguments
2
2
  import com.facebook.react.bridge.WritableMap
3
3
  import com.reactnativestripesdk.utils.mapFromPaymentSheetBillingDetails
4
4
  import com.stripe.android.paymentelement.EmbeddedPaymentElement
5
- import com.stripe.android.paymentelement.ExperimentalEmbeddedPaymentElementApi
6
5
 
7
6
  /**
8
7
  * Serialize Stripe's PaymentOptionDisplayData into a WritableMap
9
8
  * that can be sent over the RN bridge.
10
9
  */
11
- @OptIn(ExperimentalEmbeddedPaymentElementApi::class)
12
10
  fun EmbeddedPaymentElement.PaymentOptionDisplayData.toWritableMap(): WritableMap =
13
11
  Arguments.createMap().apply {
14
12
  putString("label", label)
@@ -6,7 +6,7 @@ import android.content.res.Configuration
6
6
  import android.graphics.Color
7
7
  import android.os.Bundle
8
8
  import com.reactnativestripesdk.utils.PaymentSheetAppearanceException
9
- import com.stripe.android.paymentelement.ExperimentalEmbeddedPaymentElementApi
9
+ import com.stripe.android.paymentelement.AppearanceAPIAdditionsPreview
10
10
  import com.stripe.android.paymentsheet.PaymentSheet
11
11
  import com.stripe.android.uicore.StripeThemeDefaults
12
12
 
@@ -57,6 +57,7 @@ fun buildPaymentSheetAppearance(
57
57
  )
58
58
  }
59
59
 
60
+ @OptIn(AppearanceAPIAdditionsPreview::class)
60
61
  private fun buildTypography(
61
62
  fontParams: Bundle?,
62
63
  context: Context,
@@ -244,7 +245,6 @@ private fun buildPrimaryButtonColors(
244
245
  ),
245
246
  )
246
247
 
247
- @OptIn(ExperimentalEmbeddedPaymentElementApi::class)
248
248
  @SuppressLint("RestrictedApi")
249
249
  @Throws(PaymentSheetAppearanceException::class)
250
250
  private fun buildEmbeddedAppearance(
@@ -341,7 +341,7 @@ private fun buildEmbeddedAppearance(
341
341
  val checkmarkParams = getBundleOrNull(flatParams, PaymentSheetAppearanceKeys.CHECKMARK)
342
342
  val separatorInsetsParams = getBundleOrNull(flatParams, PaymentSheetAppearanceKeys.SEPARATOR_INSETS)
343
343
 
344
- // Default separator insets specific to FlatWithCheckmark
344
+ // Default separator insets specific to FlatWithCheckmark and FlatWithChevron
345
345
  val defaultSeparatorStartInsetDp = 0.0f
346
346
  val defaultSeparatorEndInsetDp = 0.0f
347
347
 
@@ -392,6 +392,59 @@ private fun buildEmbeddedAppearance(
392
392
  colorsDark = flatCheckmarkColors,
393
393
  )
394
394
  }
395
+ "flatWithChevron" -> {
396
+ val flatParams = getBundleOrNull(rowParams, PaymentSheetAppearanceKeys.FLAT)
397
+ val chevronParams = getBundleOrNull(flatParams, PaymentSheetAppearanceKeys.CHEVRON)
398
+ val separatorInsetsParams = getBundleOrNull(flatParams, PaymentSheetAppearanceKeys.SEPARATOR_INSETS)
399
+
400
+ // Default separator insets specific to FlatWithCheckmark and FlatWithChevron
401
+ val defaultSeparatorStartInsetDp = 0.0f
402
+ val defaultSeparatorEndInsetDp = 0.0f
403
+
404
+ // Parse dimensions as Floats
405
+ val separatorThickness = getFloatOr(flatParams, PaymentSheetAppearanceKeys.SEPARATOR_THICKNESS, defaultSeparatorThicknessDp)
406
+ val startSeparatorInset = getFloatOr(separatorInsetsParams, PaymentSheetAppearanceKeys.LEFT, defaultSeparatorStartInsetDp)
407
+ val endSeparatorInset = getFloatOr(separatorInsetsParams, PaymentSheetAppearanceKeys.RIGHT, defaultSeparatorEndInsetDp)
408
+
409
+ // Parse booleans
410
+ val topEnabled = getBooleanOr(flatParams, PaymentSheetAppearanceKeys.TOP_SEPARATOR_ENABLED, true)
411
+ val bottomEnabled = getBooleanOr(flatParams, PaymentSheetAppearanceKeys.BOTTOM_SEPARATOR_ENABLED, true)
412
+
413
+ val parsedSeparatorColor =
414
+ dynamicColorFromParams(
415
+ context,
416
+ flatParams,
417
+ PaymentSheetAppearanceKeys.SEPARATOR_COLOR,
418
+ Color.GRAY,
419
+ )
420
+
421
+ val parsedChevronColor =
422
+ dynamicColorFromParams(
423
+ context,
424
+ chevronParams,
425
+ PaymentSheetAppearanceKeys.COLOR,
426
+ defaultColors.componentBorder, // Default to component border color like other elements
427
+ )
428
+
429
+ // Create the required Colors object
430
+ val flatChevronColors =
431
+ PaymentSheet.Appearance.Embedded.RowStyle.FlatWithChevron.Colors(
432
+ separatorColor = parsedSeparatorColor,
433
+ chevronColor = parsedChevronColor,
434
+ )
435
+
436
+ PaymentSheet.Appearance.Embedded.RowStyle.FlatWithChevron(
437
+ separatorThicknessDp = separatorThickness,
438
+ startSeparatorInsetDp = startSeparatorInset,
439
+ endSeparatorInsetDp = endSeparatorInset,
440
+ topSeparatorEnabled = topEnabled,
441
+ bottomSeparatorEnabled = bottomEnabled,
442
+ additionalVerticalInsetsDp = additionalInsets,
443
+ horizontalInsetsDp = 0.0F, // We do not have an iOS equal for this API so it's not configurable in React Native
444
+ colorsLight = flatChevronColors,
445
+ colorsDark = flatChevronColors,
446
+ )
447
+ }
395
448
  "floatingButton" -> {
396
449
  val floatingParams = getBundleOrNull(rowParams, PaymentSheetAppearanceKeys.FLOATING)
397
450
  PaymentSheet.Appearance.Embedded.RowStyle.FloatingButton(
@@ -606,6 +659,7 @@ private class PaymentSheetAppearanceKeys {
606
659
  const val SELECTED_COLOR = "selectedColor"
607
660
  const val UNSELECTED_COLOR = "unselectedColor"
608
661
  const val CHECKMARK = "checkmark"
662
+ const val CHEVRON = "chevron"
609
663
  const val COLOR = "color"
610
664
  const val CHECKMARK_INSET = "inset"
611
665