@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.
- package/CHANGELOG.md +10 -0
- package/README.md +1 -1
- package/android/src/main/AndroidManifest.xml +10 -0
- package/android/src/main/java/com/reactnativestripesdk/CustomPaymentMethodActivity.kt +81 -0
- package/android/src/main/java/com/reactnativestripesdk/EmbeddedPaymentElementView.kt +96 -1
- package/android/src/main/java/com/reactnativestripesdk/EmbeddedPaymentElementViewManager.kt +16 -1
- package/android/src/main/java/com/reactnativestripesdk/PaymentMethodCreateParamsFactory.kt +0 -17
- package/android/src/main/java/com/reactnativestripesdk/PaymentSheetFragment.kt +115 -19
- package/android/src/main/java/com/reactnativestripesdk/StripeSdkModule.kt +22 -0
- package/android/src/main/java/com/reactnativestripesdk/utils/Mappers.kt +52 -6
- package/android/src/main/res/values/styles.xml +27 -0
- package/android/src/oldarch/java/com/reactnativestripesdk/NativeStripeSdkModuleSpec.java +8 -0
- package/ios/ApplePayButtonManager.m +4 -0
- package/ios/ApplePayButtonView.swift +11 -4
- package/ios/Mappers.swift +0 -5
- package/ios/NewArch/ApplePayButtonComponentView.mm +6 -0
- package/ios/OldArch/StripeSdkEventEmitterCompat.h +1 -1
- package/ios/OldArch/StripeSdkEventEmitterCompat.m +7 -1
- package/ios/PaymentMethodFactory.swift +0 -17
- package/ios/StripeSdk.mm +7 -0
- package/ios/StripeSdkEmitter.swift +1 -0
- package/ios/StripeSdkImpl+Embedded.swift +39 -17
- package/ios/StripeSdkImpl+PaymentSheet.swift +114 -1
- package/ios/StripeSdkImpl.swift +38 -9
- package/lib/commonjs/components/AddToWalletButton.js +1 -1
- package/lib/commonjs/components/AddToWalletButton.js.map +1 -1
- package/lib/commonjs/components/AddressSheet.js +1 -1
- package/lib/commonjs/components/AddressSheet.js.map +1 -1
- package/lib/commonjs/components/AuBECSDebitForm.js +1 -1
- package/lib/commonjs/components/AuBECSDebitForm.js.map +1 -1
- package/lib/commonjs/components/CardField.js +1 -1
- package/lib/commonjs/components/CardField.js.map +1 -1
- package/lib/commonjs/components/CardForm.js +1 -1
- package/lib/commonjs/components/CardForm.js.map +1 -1
- package/lib/commonjs/components/PlatformPayButton.js +1 -1
- package/lib/commonjs/components/PlatformPayButton.js.map +1 -1
- package/lib/commonjs/components/StripeContainer.js +1 -1
- package/lib/commonjs/components/StripeContainer.js.map +1 -1
- package/lib/commonjs/events.js.map +1 -1
- package/lib/commonjs/functions.js +1 -1
- package/lib/commonjs/functions.js.map +1 -1
- package/lib/commonjs/specs/NativeApplePayButton.js +1 -1
- package/lib/commonjs/specs/NativeApplePayButton.js.map +1 -1
- package/lib/commonjs/specs/NativeStripeSdkModule.js.map +1 -1
- package/lib/commonjs/types/EmbeddedPaymentElement.js +1 -1
- package/lib/commonjs/types/EmbeddedPaymentElement.js.map +1 -1
- package/lib/commonjs/types/PaymentIntent.js.map +1 -1
- package/lib/commonjs/types/PaymentSheet.js +1 -1
- package/lib/commonjs/types/PaymentSheet.js.map +1 -1
- package/lib/module/components/AddToWalletButton.js +1 -1
- package/lib/module/components/AddToWalletButton.js.map +1 -1
- package/lib/module/components/AddressSheet.js +1 -1
- package/lib/module/components/AddressSheet.js.map +1 -1
- package/lib/module/components/AuBECSDebitForm.js +1 -1
- package/lib/module/components/AuBECSDebitForm.js.map +1 -1
- package/lib/module/components/CardField.js +1 -1
- package/lib/module/components/CardField.js.map +1 -1
- package/lib/module/components/CardForm.js +1 -1
- package/lib/module/components/CardForm.js.map +1 -1
- package/lib/module/components/PlatformPayButton.js +1 -1
- package/lib/module/components/PlatformPayButton.js.map +1 -1
- package/lib/module/components/StripeContainer.js +1 -1
- package/lib/module/components/StripeContainer.js.map +1 -1
- package/lib/module/events.js.map +1 -1
- package/lib/module/functions.js +1 -1
- package/lib/module/functions.js.map +1 -1
- package/lib/module/specs/NativeApplePayButton.js +1 -1
- package/lib/module/specs/NativeApplePayButton.js.map +1 -1
- package/lib/module/specs/NativeStripeSdkModule.js.map +1 -1
- package/lib/module/types/EmbeddedPaymentElement.js +1 -1
- package/lib/module/types/EmbeddedPaymentElement.js.map +1 -1
- package/lib/module/types/PaymentIntent.js.map +1 -1
- package/lib/module/types/PaymentSheet.js +1 -1
- package/lib/module/types/PaymentSheet.js.map +1 -1
- package/lib/typescript/src/components/PlatformPayButton.d.ts.map +1 -1
- package/lib/typescript/src/events.d.ts +1 -1
- package/lib/typescript/src/events.d.ts.map +1 -1
- package/lib/typescript/src/functions.d.ts.map +1 -1
- package/lib/typescript/src/specs/NativeApplePayButton.d.ts +4 -0
- package/lib/typescript/src/specs/NativeApplePayButton.d.ts.map +1 -1
- package/lib/typescript/src/specs/NativeStripeSdkModule.d.ts +2 -0
- package/lib/typescript/src/specs/NativeStripeSdkModule.d.ts.map +1 -1
- package/lib/typescript/src/types/EmbeddedPaymentElement.d.ts +2 -0
- package/lib/typescript/src/types/EmbeddedPaymentElement.d.ts.map +1 -1
- package/lib/typescript/src/types/PaymentIntent.d.ts +1 -10
- package/lib/typescript/src/types/PaymentIntent.d.ts.map +1 -1
- package/lib/typescript/src/types/PaymentMethod.d.ts +2 -13
- package/lib/typescript/src/types/PaymentMethod.d.ts.map +1 -1
- package/lib/typescript/src/types/PaymentSheet.d.ts +55 -0
- package/lib/typescript/src/types/PaymentSheet.d.ts.map +1 -1
- package/package.json +1 -1
- package/patches/README.md +55 -0
- package/patches/old-arch-codegen-fix.patch +87 -0
- package/src/components/PlatformPayButton.tsx +12 -4
- package/src/events.ts +2 -1
- package/src/functions.ts +36 -1
- package/src/specs/NativeApplePayButton.ts +5 -0
- package/src/specs/NativeStripeSdkModule.ts +4 -0
- package/src/types/EmbeddedPaymentElement.tsx +33 -0
- package/src/types/PaymentIntent.ts +0 -10
- package/src/types/PaymentMethod.ts +0 -14
- package/src/types/PaymentSheet.ts +59 -0
- package/.env +0 -19
- package/ios/StripeSdk.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -7
- package/ios/StripeSdk.xcodeproj/project.xcworkspace/xcuserdata/wooj.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- 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,
|
|
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
|
-
).
|
|
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(
|
|
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 :
|
|
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
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
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
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
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
|
-
|
|
275
|
-
createIntentCallback
|
|
276
|
-
|
|
277
|
-
|
|
291
|
+
PaymentSheet
|
|
292
|
+
.Builder(paymentResultCallback)
|
|
293
|
+
.createIntentCallback(createIntentCallback)
|
|
294
|
+
.confirmCustomPaymentMethodCallback(this)
|
|
295
|
+
.build(this)
|
|
278
296
|
} else {
|
|
279
|
-
PaymentSheet
|
|
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,
|