@stripe/stripe-react-native 0.49.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 (106) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +1 -1
  3. package/android/src/main/AndroidManifest.xml +10 -0
  4. package/android/src/main/java/com/reactnativestripesdk/CustomPaymentMethodActivity.kt +81 -0
  5. package/android/src/main/java/com/reactnativestripesdk/EmbeddedPaymentElementView.kt +96 -1
  6. package/android/src/main/java/com/reactnativestripesdk/EmbeddedPaymentElementViewManager.kt +16 -1
  7. package/android/src/main/java/com/reactnativestripesdk/PaymentMethodCreateParamsFactory.kt +0 -17
  8. package/android/src/main/java/com/reactnativestripesdk/PaymentSheetFragment.kt +115 -19
  9. package/android/src/main/java/com/reactnativestripesdk/StripeSdkModule.kt +22 -0
  10. package/android/src/main/java/com/reactnativestripesdk/utils/Mappers.kt +52 -6
  11. package/android/src/main/res/values/styles.xml +27 -0
  12. package/android/src/oldarch/java/com/reactnativestripesdk/NativeStripeSdkModuleSpec.java +8 -0
  13. package/ios/ApplePayButtonManager.m +4 -0
  14. package/ios/ApplePayButtonView.swift +11 -4
  15. package/ios/Mappers.swift +0 -5
  16. package/ios/NewArch/ApplePayButtonComponentView.mm +6 -0
  17. package/ios/OldArch/StripeSdkEventEmitterCompat.h +1 -1
  18. package/ios/OldArch/StripeSdkEventEmitterCompat.m +7 -1
  19. package/ios/PaymentMethodFactory.swift +0 -17
  20. package/ios/StripeSdk.mm +7 -0
  21. package/ios/StripeSdkEmitter.swift +1 -0
  22. package/ios/StripeSdkImpl+Embedded.swift +39 -17
  23. package/ios/StripeSdkImpl+PaymentSheet.swift +114 -1
  24. package/ios/StripeSdkImpl.swift +38 -9
  25. package/lib/commonjs/components/AddToWalletButton.js +1 -1
  26. package/lib/commonjs/components/AddToWalletButton.js.map +1 -1
  27. package/lib/commonjs/components/AddressSheet.js +1 -1
  28. package/lib/commonjs/components/AddressSheet.js.map +1 -1
  29. package/lib/commonjs/components/AuBECSDebitForm.js +1 -1
  30. package/lib/commonjs/components/AuBECSDebitForm.js.map +1 -1
  31. package/lib/commonjs/components/CardField.js +1 -1
  32. package/lib/commonjs/components/CardField.js.map +1 -1
  33. package/lib/commonjs/components/CardForm.js +1 -1
  34. package/lib/commonjs/components/CardForm.js.map +1 -1
  35. package/lib/commonjs/components/PlatformPayButton.js +1 -1
  36. package/lib/commonjs/components/PlatformPayButton.js.map +1 -1
  37. package/lib/commonjs/components/StripeContainer.js +1 -1
  38. package/lib/commonjs/components/StripeContainer.js.map +1 -1
  39. package/lib/commonjs/events.js.map +1 -1
  40. package/lib/commonjs/functions.js +1 -1
  41. package/lib/commonjs/functions.js.map +1 -1
  42. package/lib/commonjs/specs/NativeApplePayButton.js +1 -1
  43. package/lib/commonjs/specs/NativeApplePayButton.js.map +1 -1
  44. package/lib/commonjs/specs/NativeStripeSdkModule.js.map +1 -1
  45. package/lib/commonjs/types/EmbeddedPaymentElement.js +1 -1
  46. package/lib/commonjs/types/EmbeddedPaymentElement.js.map +1 -1
  47. package/lib/commonjs/types/PaymentIntent.js.map +1 -1
  48. package/lib/commonjs/types/PaymentSheet.js +1 -1
  49. package/lib/commonjs/types/PaymentSheet.js.map +1 -1
  50. package/lib/module/components/AddToWalletButton.js +1 -1
  51. package/lib/module/components/AddToWalletButton.js.map +1 -1
  52. package/lib/module/components/AddressSheet.js +1 -1
  53. package/lib/module/components/AddressSheet.js.map +1 -1
  54. package/lib/module/components/AuBECSDebitForm.js +1 -1
  55. package/lib/module/components/AuBECSDebitForm.js.map +1 -1
  56. package/lib/module/components/CardField.js +1 -1
  57. package/lib/module/components/CardField.js.map +1 -1
  58. package/lib/module/components/CardForm.js +1 -1
  59. package/lib/module/components/CardForm.js.map +1 -1
  60. package/lib/module/components/PlatformPayButton.js +1 -1
  61. package/lib/module/components/PlatformPayButton.js.map +1 -1
  62. package/lib/module/components/StripeContainer.js +1 -1
  63. package/lib/module/components/StripeContainer.js.map +1 -1
  64. package/lib/module/events.js.map +1 -1
  65. package/lib/module/functions.js +1 -1
  66. package/lib/module/functions.js.map +1 -1
  67. package/lib/module/specs/NativeApplePayButton.js +1 -1
  68. package/lib/module/specs/NativeApplePayButton.js.map +1 -1
  69. package/lib/module/specs/NativeStripeSdkModule.js.map +1 -1
  70. package/lib/module/types/EmbeddedPaymentElement.js +1 -1
  71. package/lib/module/types/EmbeddedPaymentElement.js.map +1 -1
  72. package/lib/module/types/PaymentIntent.js.map +1 -1
  73. package/lib/module/types/PaymentSheet.js +1 -1
  74. package/lib/module/types/PaymentSheet.js.map +1 -1
  75. package/lib/typescript/src/components/PlatformPayButton.d.ts.map +1 -1
  76. package/lib/typescript/src/events.d.ts +1 -1
  77. package/lib/typescript/src/events.d.ts.map +1 -1
  78. package/lib/typescript/src/functions.d.ts.map +1 -1
  79. package/lib/typescript/src/specs/NativeApplePayButton.d.ts +4 -0
  80. package/lib/typescript/src/specs/NativeApplePayButton.d.ts.map +1 -1
  81. package/lib/typescript/src/specs/NativeStripeSdkModule.d.ts +2 -0
  82. package/lib/typescript/src/specs/NativeStripeSdkModule.d.ts.map +1 -1
  83. package/lib/typescript/src/types/EmbeddedPaymentElement.d.ts +2 -0
  84. package/lib/typescript/src/types/EmbeddedPaymentElement.d.ts.map +1 -1
  85. package/lib/typescript/src/types/PaymentIntent.d.ts +1 -10
  86. package/lib/typescript/src/types/PaymentIntent.d.ts.map +1 -1
  87. package/lib/typescript/src/types/PaymentMethod.d.ts +2 -13
  88. package/lib/typescript/src/types/PaymentMethod.d.ts.map +1 -1
  89. package/lib/typescript/src/types/PaymentSheet.d.ts +55 -0
  90. package/lib/typescript/src/types/PaymentSheet.d.ts.map +1 -1
  91. package/package.json +1 -1
  92. package/patches/README.md +55 -0
  93. package/patches/old-arch-codegen-fix.patch +87 -0
  94. package/src/components/PlatformPayButton.tsx +12 -4
  95. package/src/events.ts +2 -1
  96. package/src/functions.ts +36 -1
  97. package/src/specs/NativeApplePayButton.ts +5 -0
  98. package/src/specs/NativeStripeSdkModule.ts +4 -0
  99. package/src/types/EmbeddedPaymentElement.tsx +33 -0
  100. package/src/types/PaymentIntent.ts +0 -10
  101. package/src/types/PaymentMethod.ts +0 -14
  102. package/src/types/PaymentSheet.ts +59 -0
  103. package/.env +0 -19
  104. package/ios/StripeSdk.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -7
  105. package/ios/StripeSdk.xcodeproj/project.xcworkspace/xcuserdata/wooj.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  106. package/ios/StripeSdk.xcodeproj/xcuserdata/wooj.xcuserdatad/xcschemes/xcschememanagement.plist +0 -19
package/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
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
+
3
13
  ## 0.49.0 - 2025-07-02
4
14
 
5
15
  **Features**
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
 
@@ -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,6 +1,8 @@
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
@@ -9,6 +11,7 @@ import androidx.compose.runtime.getValue
9
11
  import androidx.compose.runtime.mutableIntStateOf
10
12
  import androidx.compose.runtime.mutableStateOf
11
13
  import androidx.compose.runtime.remember
14
+ import androidx.compose.runtime.rememberCoroutineScope
12
15
  import androidx.compose.runtime.setValue
13
16
  import androidx.compose.ui.Modifier
14
17
  import androidx.compose.ui.layout.layout
@@ -19,14 +22,21 @@ import androidx.compose.ui.unit.dp
19
22
  import com.facebook.react.bridge.Arguments
20
23
  import com.facebook.react.uimanager.ThemedReactContext
21
24
  import com.reactnativestripesdk.utils.KeepJsAwakeTask
25
+ import com.reactnativestripesdk.utils.mapFromCustomPaymentMethod
22
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
23
30
  import com.stripe.android.paymentelement.EmbeddedPaymentElement
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
42
  enum class RowSelectionBehaviorType {
@@ -34,6 +44,7 @@ enum class RowSelectionBehaviorType {
34
44
  ImmediateAction,
35
45
  }
36
46
 
47
+ @OptIn(ExperimentalCustomPaymentMethodsApi::class)
37
48
  class EmbeddedPaymentElementView(
38
49
  context: Context,
39
50
  ) : StripeAbstractComposeView(context) {
@@ -56,9 +67,92 @@ class EmbeddedPaymentElementView(
56
67
  private val reactContext get() = context as ThemedReactContext
57
68
  private val events = Channel<Event>(Channel.UNLIMITED)
58
69
 
70
+ @OptIn(ExperimentalCustomPaymentMethodsApi::class)
59
71
  @Composable
60
72
  override fun Content() {
61
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.
83
+ try {
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)
92
+ }
93
+
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
+ }
102
+
103
+ // Keep JS awake while React Native is backgrounded by Stripe SDK.
104
+ val keepJsAwakeTask =
105
+ KeepJsAwakeTask(reactContext.reactApplicationContext).apply { start() }
106
+
107
+ // Run on coroutine scope.
108
+ coroutineScope.launch {
109
+ try {
110
+ // Give the CustomPaymentMethodActivity a moment to fully initialize
111
+ delay(100)
112
+
113
+ // Emit event so JS can show the Alert and eventually respond via `customPaymentMethodResultCallback`.
114
+ stripeSdkModule.emitOnCustomPaymentMethodConfirmHandlerCallback(
115
+ mapFromCustomPaymentMethod(customPaymentMethod, billingDetails),
116
+ )
117
+
118
+ // Await JS result.
119
+ val resultFromJs = stripeSdkModule.customPaymentMethodResultCallback.await()
120
+
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
+ }
153
+ }
154
+ }
155
+
62
156
  val builder =
63
157
  remember(type) {
64
158
  EmbeddedPaymentElement
@@ -125,7 +219,8 @@ class EmbeddedPaymentElementView(
125
219
  }
126
220
  requireStripeSdkModule().emitEmbeddedPaymentElementFormSheetConfirmComplete(map)
127
221
  },
128
- ).rowSelectionBehavior(
222
+ ).confirmCustomPaymentMethodCallback(confirmCustomPaymentMethodCallback)
223
+ .rowSelectionBehavior(
129
224
  if (type == RowSelectionBehaviorType.Default) {
130
225
  EmbeddedPaymentElement.RowSelectionBehavior.default()
131
226
  } else {
@@ -18,9 +18,11 @@ 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
25
+ import com.stripe.android.paymentelement.ExperimentalCustomPaymentMethodsApi
24
26
  import com.stripe.android.paymentsheet.PaymentSheet
25
27
 
26
28
  @ReactModule(name = EmbeddedPaymentElementViewManager.NAME)
@@ -80,7 +82,10 @@ class EmbeddedPaymentElementViewManager :
80
82
  }
81
83
 
82
84
  @SuppressLint("RestrictedApi")
83
- @OptIn(ExperimentalAllowsRemovalOfLastSavedPaymentMethodApi::class)
85
+ @OptIn(
86
+ ExperimentalAllowsRemovalOfLastSavedPaymentMethodApi::class,
87
+ ExperimentalCustomPaymentMethodsApi::class,
88
+ )
84
89
  private fun parseElementConfiguration(
85
90
  map: ReadableMap,
86
91
  context: Context,
@@ -184,6 +189,16 @@ class EmbeddedPaymentElementViewManager :
184
189
  ),
185
190
  ).allowsRemovalOfLastSavedPaymentMethod(allowsRemovalOfLastSavedPaymentMethod)
186
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
+ )
187
202
 
188
203
  primaryButtonLabel?.let { configurationBuilder.primaryButtonLabel(it) }
189
204
  paymentMethodOrder?.let { configurationBuilder.paymentMethodOrder(it) }
@@ -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,
@@ -3,6 +3,7 @@ package com.reactnativestripesdk
3
3
  import android.app.Activity
4
4
  import android.app.Application
5
5
  import android.content.Context
6
+ import android.content.Intent
6
7
  import android.graphics.Bitmap
7
8
  import android.graphics.Canvas
8
9
  import android.graphics.Color
@@ -11,6 +12,7 @@ import android.os.Bundle
11
12
  import android.os.Handler
12
13
  import android.os.Looper
13
14
  import android.util.Base64
15
+ import android.util.Log
14
16
  import androidx.appcompat.content.res.AppCompatResources
15
17
  import androidx.core.graphics.drawable.DrawableCompat
16
18
  import com.facebook.react.bridge.Arguments
@@ -28,11 +30,17 @@ import com.reactnativestripesdk.utils.PaymentSheetException
28
30
  import com.reactnativestripesdk.utils.StripeFragment
29
31
  import com.reactnativestripesdk.utils.createError
30
32
  import com.reactnativestripesdk.utils.createResult
33
+ import com.reactnativestripesdk.utils.mapFromCustomPaymentMethod
31
34
  import com.reactnativestripesdk.utils.mapFromPaymentMethod
32
35
  import com.reactnativestripesdk.utils.mapToPreferredNetworks
36
+ import com.reactnativestripesdk.utils.parseCustomPaymentMethods
33
37
  import com.reactnativestripesdk.utils.removeFragment
34
38
  import com.stripe.android.ExperimentalAllowsRemovalOfLastSavedPaymentMethodApi
35
39
  import com.stripe.android.model.PaymentMethod
40
+ import com.stripe.android.paymentelement.ConfirmCustomPaymentMethodCallback
41
+ import com.stripe.android.paymentelement.CustomPaymentMethodResult
42
+ import com.stripe.android.paymentelement.CustomPaymentMethodResultHandler
43
+ import com.stripe.android.paymentelement.ExperimentalCustomPaymentMethodsApi
36
44
  import com.stripe.android.paymentelement.PaymentMethodOptionsSetupFutureUsagePreview
37
45
  import com.stripe.android.paymentsheet.CreateIntentCallback
38
46
  import com.stripe.android.paymentsheet.CreateIntentResult
@@ -42,11 +50,17 @@ import com.stripe.android.paymentsheet.PaymentSheet
42
50
  import com.stripe.android.paymentsheet.PaymentSheetResult
43
51
  import com.stripe.android.paymentsheet.PaymentSheetResultCallback
44
52
  import kotlinx.coroutines.CompletableDeferred
53
+ import kotlinx.coroutines.CoroutineScope
54
+ import kotlinx.coroutines.Dispatchers
55
+ import kotlinx.coroutines.delay
56
+ import kotlinx.coroutines.launch
45
57
  import java.io.ByteArrayOutputStream
46
58
  import kotlin.Exception
47
59
 
48
- @OptIn(ExperimentalAllowsRemovalOfLastSavedPaymentMethodApi::class)
49
- class PaymentSheetFragment : StripeFragment() {
60
+ @OptIn(ExperimentalAllowsRemovalOfLastSavedPaymentMethodApi::class, ExperimentalCustomPaymentMethodsApi::class)
61
+ class PaymentSheetFragment :
62
+ StripeFragment(),
63
+ ConfirmCustomPaymentMethodCallback {
50
64
  private lateinit var context: ReactApplicationContext
51
65
  private lateinit var initPromise: Promise
52
66
  private var paymentSheet: PaymentSheet? = null
@@ -61,6 +75,7 @@ class PaymentSheetFragment : StripeFragment() {
61
75
  internal var paymentSheetIntentCreationCallback = CompletableDeferred<ReadableMap>()
62
76
  private var keepJsAwake: KeepJsAwakeTask? = null
63
77
 
78
+ @OptIn(ExperimentalCustomPaymentMethodsApi::class)
64
79
  override fun prepare() {
65
80
  val merchantDisplayName = arguments?.getString("merchantDisplayName").orEmpty()
66
81
  if (merchantDisplayName.isEmpty()) {
@@ -240,6 +255,7 @@ class PaymentSheetFragment : StripeFragment() {
240
255
  mapToPreferredNetworks(arguments?.getIntegerArrayList("preferredNetworks")),
241
256
  ).allowsRemovalOfLastSavedPaymentMethod(allowsRemovalOfLastSavedPaymentMethod)
242
257
  .cardBrandAcceptance(mapToCardBrandAcceptance(arguments))
258
+ .customPaymentMethods(parseCustomPaymentMethods(arguments))
243
259
 
244
260
  primaryButtonLabel?.let { configurationBuilder.primaryButtonLabel(it) }
245
261
  paymentMethodOrder?.let { configurationBuilder.paymentMethodOrder(it) }
@@ -253,30 +269,35 @@ class PaymentSheetFragment : StripeFragment() {
253
269
  if (arguments?.getBoolean("customFlow") == true) {
254
270
  flowController =
255
271
  if (intentConfiguration != null) {
256
- PaymentSheet.FlowController.create(
257
- this,
258
- paymentOptionCallback = paymentOptionCallback,
259
- createIntentCallback = createIntentCallback,
260
- paymentResultCallback = paymentResultCallback,
261
- )
272
+ PaymentSheet.FlowController
273
+ .Builder(
274
+ resultCallback = paymentResultCallback,
275
+ paymentOptionCallback = paymentOptionCallback,
276
+ ).createIntentCallback(createIntentCallback)
277
+ .confirmCustomPaymentMethodCallback(this)
278
+ .build(this)
262
279
  } else {
263
- PaymentSheet.FlowController.create(
264
- this,
265
- paymentOptionCallback = paymentOptionCallback,
266
- paymentResultCallback = paymentResultCallback,
267
- )
280
+ PaymentSheet.FlowController
281
+ .Builder(
282
+ resultCallback = paymentResultCallback,
283
+ paymentOptionCallback = paymentOptionCallback,
284
+ ).confirmCustomPaymentMethodCallback(this)
285
+ .build(this)
268
286
  }
269
287
  configureFlowController()
270
288
  } else {
271
289
  paymentSheet =
272
290
  if (intentConfiguration != null) {
273
- PaymentSheet(
274
- this,
275
- createIntentCallback = createIntentCallback,
276
- paymentResultCallback = paymentResultCallback,
277
- )
291
+ PaymentSheet
292
+ .Builder(paymentResultCallback)
293
+ .createIntentCallback(createIntentCallback)
294
+ .confirmCustomPaymentMethodCallback(this)
295
+ .build(this)
278
296
  } else {
279
- PaymentSheet(this, callback = paymentResultCallback)
297
+ PaymentSheet
298
+ .Builder(paymentResultCallback)
299
+ .confirmCustomPaymentMethodCallback(this)
300
+ .build(this)
280
301
  }
281
302
  initPromise.resolve(WritableNativeMap())
282
303
  }
@@ -420,6 +441,81 @@ class PaymentSheetFragment : StripeFragment() {
420
441
  } ?: run { resolvePresentPromise(map) }
421
442
  }
422
443
 
444
+ @OptIn(ExperimentalCustomPaymentMethodsApi::class)
445
+ override fun onConfirmCustomPaymentMethod(
446
+ customPaymentMethod: PaymentSheet.CustomPaymentMethod,
447
+ billingDetails: PaymentMethod.BillingDetails,
448
+ ) {
449
+ // Launch a transparent Activity to ensure React Native UI can appear on top of the Stripe proxy activity.
450
+ try {
451
+ val intent =
452
+ Intent(context, CustomPaymentMethodActivity::class.java).apply {
453
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
454
+ addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)
455
+ }
456
+ context.startActivity(intent)
457
+ } catch (e: Exception) {
458
+ Log.e("StripeReactNative", "Failed to start CustomPaymentMethodActivity", e)
459
+ }
460
+
461
+ val stripeSdkModule =
462
+ try {
463
+ context.getNativeModule(StripeSdkModule::class.java)
464
+ ?: throw IllegalArgumentException("StripeSdkModule not found")
465
+ } catch (ex: IllegalArgumentException) {
466
+ Log.e("StripeReactNative", "StripeSdkModule not found for CPM callback", ex)
467
+ CustomPaymentMethodActivity.finishCurrent()
468
+ return
469
+ }
470
+
471
+ // Keep JS awake while React Native is backgrounded by Stripe SDK.
472
+ val keepJsAwakeTask =
473
+ KeepJsAwakeTask(context).apply { start() }
474
+
475
+ // Run on main coroutine scope.
476
+ CoroutineScope(Dispatchers.Main).launch {
477
+ try {
478
+ // Give the CustomPaymentMethodActivity a moment to fully initialize
479
+ delay(100)
480
+
481
+ // Emit event so JS can show the Alert and eventually respond via `customPaymentMethodResultCallback`.
482
+ stripeSdkModule.emitOnCustomPaymentMethodConfirmHandlerCallback(
483
+ mapFromCustomPaymentMethod(customPaymentMethod, billingDetails),
484
+ )
485
+
486
+ // Await JS result.
487
+ val resultFromJs = stripeSdkModule.customPaymentMethodResultCallback.await()
488
+
489
+ keepJsAwakeTask.stop()
490
+
491
+ val status = resultFromJs.getString("status")
492
+
493
+ val nativeResult =
494
+ when (status) {
495
+ "completed" ->
496
+ CustomPaymentMethodResult.completed()
497
+ "canceled" ->
498
+ CustomPaymentMethodResult.canceled()
499
+ "failed" -> {
500
+ val errMsg = resultFromJs.getString("error") ?: "Custom payment failed"
501
+ CustomPaymentMethodResult.failed(displayMessage = errMsg)
502
+ }
503
+ else ->
504
+ CustomPaymentMethodResult.failed(displayMessage = "Unknown status")
505
+ }
506
+
507
+ // Return result to Stripe SDK.
508
+ CustomPaymentMethodResultHandler.handleCustomPaymentMethodResult(
509
+ context,
510
+ nativeResult,
511
+ )
512
+ } finally {
513
+ // Clean up the transparent activity
514
+ CustomPaymentMethodActivity.finishCurrent()
515
+ }
516
+ }
517
+ }
518
+
423
519
  companion object {
424
520
  internal const val TAG = "payment_sheet_launch_fragment"
425
521
 
@@ -8,6 +8,7 @@ import android.os.Bundle
8
8
  import android.util.Log
9
9
  import android.view.ViewGroup
10
10
  import androidx.fragment.app.FragmentActivity
11
+ import com.facebook.react.bridge.Arguments
11
12
  import com.facebook.react.bridge.BaseActivityEventListener
12
13
  import com.facebook.react.bridge.Promise
13
14
  import com.facebook.react.bridge.ReactApplicationContext
@@ -92,6 +93,7 @@ class StripeSdkModule(
92
93
  private var customerSheetFragment: CustomerSheetFragment? = null
93
94
 
94
95
  internal var embeddedIntentCreationCallback = CompletableDeferred<ReadableMap>()
96
+ internal var customPaymentMethodResultCallback = CompletableDeferred<ReadableMap>()
95
97
 
96
98
  internal var composeCompatView: StripeAbstractComposeView.CompatView? = null
97
99
 
@@ -233,6 +235,14 @@ class StripeSdkModule(
233
235
  getCurrentActivityOrResolveWithError(promise)?.let { activity ->
234
236
  paymentSheetFragment?.removeFragment(reactApplicationContext)
235
237
  val bundle = toBundleObject(params)
238
+
239
+ // Handle custom payment methods separately since toBundleObject cannot handle arrays of objects
240
+ val customPaymentMethodConfig = params.getMap("customPaymentMethodConfiguration")
241
+ if (customPaymentMethodConfig != null) {
242
+ // Store the original ReadableMap for custom payment methods
243
+ bundle.putSerializable("customPaymentMethodConfigurationReadableMap", customPaymentMethodConfig.toHashMap())
244
+ }
245
+
236
246
  paymentSheetFragment =
237
247
  PaymentSheetFragment.create(reactApplicationContext, bundle, promise)
238
248
  try {
@@ -298,6 +308,18 @@ class StripeSdkModule(
298
308
  paymentSheetFragment?.paymentSheetIntentCreationCallback?.complete(params)
299
309
  }
300
310
 
311
+ @ReactMethod
312
+ override fun customPaymentMethodResultCallback(
313
+ result: ReadableMap?,
314
+ promise: Promise?,
315
+ ) {
316
+ // Complete the deferred with the result from JavaScript
317
+ customPaymentMethodResultCallback.complete(result ?: Arguments.createMap())
318
+ // Reset for next use
319
+ customPaymentMethodResultCallback = CompletableDeferred()
320
+ promise?.resolve(null)
321
+ }
322
+
301
323
  @ReactMethod
302
324
  override fun createPaymentMethod(
303
325
  data: ReadableMap,