@stripe/stripe-react-native 0.58.0 → 0.59.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/android/build.gradle +2 -0
- package/android/src/main/AndroidManifest.xml +27 -1
- package/android/src/main/java/com/reactnativestripesdk/EmbeddedPaymentElementViewManager.kt +2 -0
- package/android/src/main/java/com/reactnativestripesdk/EventEmitterCompat.kt +8 -0
- package/android/src/main/java/com/reactnativestripesdk/PaymentElementConfig.kt +8 -0
- package/android/src/main/java/com/reactnativestripesdk/PaymentMethodMessagingElementConfig.kt +147 -0
- package/android/src/main/java/com/reactnativestripesdk/PaymentMethodMessagingElementView.kt +164 -0
- package/android/src/main/java/com/reactnativestripesdk/PaymentMethodMessagingElementViewManager.kt +65 -0
- package/android/src/main/java/com/reactnativestripesdk/PaymentSheetAppearance.kt +1 -1
- package/android/src/main/java/com/reactnativestripesdk/PaymentSheetManager.kt +55 -26
- package/android/src/main/java/com/reactnativestripesdk/StripeConnectDeepLinkInterceptorActivity.kt +77 -0
- package/android/src/main/java/com/reactnativestripesdk/StripeSdkModule.kt +325 -22
- package/android/src/main/java/com/reactnativestripesdk/StripeSdkPackage.kt +1 -0
- package/android/src/main/java/com/reactnativestripesdk/customersheet/CustomerSheetManager.kt +3 -0
- package/android/src/main/java/com/reactnativestripesdk/utils/Errors.kt +8 -0
- package/android/src/main/res/xml/file_paths.xml +4 -0
- package/android/src/oldarch/java/com/facebook/react/viewmanagers/PaymentMethodMessagingElementViewManagerDelegate.java +36 -0
- package/android/src/oldarch/java/com/facebook/react/viewmanagers/PaymentMethodMessagingElementViewManagerInterface.java +18 -0
- package/android/src/oldarch/java/com/reactnativestripesdk/NativeStripeSdkModuleSpec.java +20 -0
- package/android/src/test/java/com/reactnativestripesdk/PaymentElementConfigTest.kt +37 -0
- package/android/src/test/java/com/reactnativestripesdk/PaymentMethodMessagingElementConfigTest.kt +543 -0
- package/android/src/test/java/com/reactnativestripesdk/PaymentSheetManagerTest.kt +70 -0
- package/ios/CustomerSheet/CustomerSheetUtils.swift +4 -0
- package/ios/OldArch/StripeSdkEventEmitterCompat.h +2 -0
- package/ios/OldArch/StripeSdkEventEmitterCompat.m +13 -1
- package/ios/PaymentMethodMessagingElementConfig.swift +116 -0
- package/ios/PaymentMethodMessagingElementHandler.m +9 -0
- package/ios/PaymentMethodMessagingElementView.swift +139 -0
- package/ios/StripeSdk.mm +40 -0
- package/ios/StripeSdkEmitter.swift +2 -0
- package/ios/StripeSdkImpl+CustomerSheet.swift +1 -0
- package/ios/StripeSdkImpl+Embedded.swift +4 -0
- package/ios/StripeSdkImpl+PaymentSheet.swift +16 -0
- package/ios/StripeSdkImpl.swift +132 -0
- package/jest/mock.js +20 -0
- package/lib/commonjs/connect/Components.js.map +1 -1
- package/lib/commonjs/connect/ConnectComponentsProvider.js +1 -1
- package/lib/commonjs/connect/ConnectComponentsProvider.js.map +1 -1
- package/lib/commonjs/connect/EmbeddedComponent.js +5 -5
- package/lib/commonjs/connect/EmbeddedComponent.js.map +1 -1
- package/lib/commonjs/connect/analytics/AnalyticsClient.js +2 -0
- package/lib/commonjs/connect/analytics/AnalyticsClient.js.map +1 -0
- package/lib/commonjs/connect/analytics/ComponentAnalyticsClient.js +2 -0
- package/lib/commonjs/connect/analytics/ComponentAnalyticsClient.js.map +1 -0
- package/lib/commonjs/connect/analytics/events.js +2 -0
- package/lib/commonjs/connect/analytics/events.js.map +1 -0
- package/lib/commonjs/connect/testUtils.js +2 -0
- package/lib/commonjs/connect/testUtils.js.map +1 -0
- 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/hooks/useStripe.js +1 -1
- package/lib/commonjs/hooks/useStripe.js.map +1 -1
- package/lib/commonjs/specs/NativePaymentMethodMessagingElement.js +2 -0
- package/lib/commonjs/specs/NativePaymentMethodMessagingElement.js.map +1 -0
- package/lib/commonjs/specs/NativeStripeSdkModule.js.map +1 -1
- package/lib/commonjs/types/EmbeddedPaymentElement.js.map +1 -1
- package/lib/commonjs/types/Errors.js +1 -1
- package/lib/commonjs/types/Errors.js.map +1 -1
- package/lib/commonjs/types/PaymentSheet.js.map +1 -1
- package/lib/commonjs/types/components/PaymentMethodMessagingElementComponent.js +2 -0
- package/lib/commonjs/types/components/PaymentMethodMessagingElementComponent.js.map +1 -0
- package/lib/commonjs/types/index.js.map +1 -1
- package/lib/module/connect/Components.js.map +1 -1
- package/lib/module/connect/ConnectComponentsProvider.js +1 -1
- package/lib/module/connect/ConnectComponentsProvider.js.map +1 -1
- package/lib/module/connect/EmbeddedComponent.js +5 -5
- package/lib/module/connect/EmbeddedComponent.js.map +1 -1
- package/lib/module/connect/analytics/AnalyticsClient.js +2 -0
- package/lib/module/connect/analytics/AnalyticsClient.js.map +1 -0
- package/lib/module/connect/analytics/ComponentAnalyticsClient.js +2 -0
- package/lib/module/connect/analytics/ComponentAnalyticsClient.js.map +1 -0
- package/lib/module/connect/analytics/events.js +2 -0
- package/lib/module/connect/analytics/events.js.map +1 -0
- package/lib/module/connect/testUtils.js +2 -0
- package/lib/module/connect/testUtils.js.map +1 -0
- 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/hooks/useStripe.js +1 -1
- package/lib/module/hooks/useStripe.js.map +1 -1
- package/lib/module/specs/NativePaymentMethodMessagingElement.js +2 -0
- package/lib/module/specs/NativePaymentMethodMessagingElement.js.map +1 -0
- package/lib/module/specs/NativeStripeSdkModule.js.map +1 -1
- package/lib/module/types/EmbeddedPaymentElement.js.map +1 -1
- package/lib/module/types/Errors.js +1 -1
- package/lib/module/types/Errors.js.map +1 -1
- package/lib/module/types/PaymentSheet.js.map +1 -1
- package/lib/module/types/components/PaymentMethodMessagingElementComponent.js +2 -0
- package/lib/module/types/components/PaymentMethodMessagingElementComponent.js.map +1 -0
- package/lib/module/types/index.js.map +1 -1
- package/lib/typescript/src/connect/Components.d.ts +91 -0
- package/lib/typescript/src/connect/Components.d.ts.map +1 -1
- package/lib/typescript/src/connect/ConnectComponentsProvider.d.ts +61 -0
- package/lib/typescript/src/connect/ConnectComponentsProvider.d.ts.map +1 -1
- package/lib/typescript/src/connect/EmbeddedComponent.d.ts.map +1 -1
- package/lib/typescript/src/connect/analytics/AnalyticsClient.d.ts +32 -0
- package/lib/typescript/src/connect/analytics/AnalyticsClient.d.ts.map +1 -0
- package/lib/typescript/src/connect/analytics/ComponentAnalyticsClient.d.ts +94 -0
- package/lib/typescript/src/connect/analytics/ComponentAnalyticsClient.d.ts.map +1 -0
- package/lib/typescript/src/connect/analytics/events.d.ts +215 -0
- package/lib/typescript/src/connect/analytics/events.d.ts.map +1 -0
- package/lib/typescript/src/connect/testUtils.d.ts +45 -0
- package/lib/typescript/src/connect/testUtils.d.ts.map +1 -0
- package/lib/typescript/src/events.d.ts +2 -0
- package/lib/typescript/src/events.d.ts.map +1 -1
- package/lib/typescript/src/functions.d.ts +13 -1
- package/lib/typescript/src/functions.d.ts.map +1 -1
- package/lib/typescript/src/hooks/useStripe.d.ts +2 -1
- package/lib/typescript/src/hooks/useStripe.d.ts.map +1 -1
- package/lib/typescript/src/specs/NativePaymentMethodMessagingElement.d.ts +16 -0
- package/lib/typescript/src/specs/NativePaymentMethodMessagingElement.d.ts.map +1 -0
- package/lib/typescript/src/specs/NativeStripeSdkModule.d.ts +16 -1
- package/lib/typescript/src/specs/NativeStripeSdkModule.d.ts.map +1 -1
- package/lib/typescript/src/types/CustomerSheet.d.ts +5 -0
- package/lib/typescript/src/types/CustomerSheet.d.ts.map +1 -1
- package/lib/typescript/src/types/EmbeddedPaymentElement.d.ts +5 -0
- package/lib/typescript/src/types/EmbeddedPaymentElement.d.ts.map +1 -1
- package/lib/typescript/src/types/Errors.d.ts +4 -0
- package/lib/typescript/src/types/Errors.d.ts.map +1 -1
- package/lib/typescript/src/types/PaymentSheet.d.ts +5 -0
- package/lib/typescript/src/types/PaymentSheet.d.ts.map +1 -1
- package/lib/typescript/src/types/components/PaymentMethodMessagingElementComponent.d.ts +69 -0
- package/lib/typescript/src/types/components/PaymentMethodMessagingElementComponent.d.ts.map +1 -0
- package/lib/typescript/src/types/index.d.ts +8 -1
- package/lib/typescript/src/types/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/connect/Components.tsx +91 -0
- package/src/connect/ConnectComponentsProvider.tsx +69 -2
- package/src/connect/EmbeddedComponent.tsx +254 -30
- package/src/connect/analytics/AnalyticsClient.ts +75 -0
- package/src/connect/analytics/ComponentAnalyticsClient.ts +315 -0
- package/src/connect/analytics/events.ts +253 -0
- package/src/connect/testUtils.ts +37 -0
- package/src/events.ts +2 -0
- package/src/functions.ts +10 -0
- package/src/hooks/useStripe.tsx +8 -0
- package/src/specs/NativePaymentMethodMessagingElement.ts +25 -0
- package/src/specs/NativeStripeSdkModule.ts +21 -1
- package/src/types/CustomerSheet.ts +5 -0
- package/src/types/EmbeddedPaymentElement.tsx +5 -0
- package/src/types/Errors.ts +5 -0
- package/src/types/PaymentSheet.ts +6 -1
- package/src/types/components/PaymentMethodMessagingElementComponent.tsx +74 -0
- package/src/types/index.ts +11 -0
package/android/build.gradle
CHANGED
|
@@ -100,6 +100,7 @@ android {
|
|
|
100
100
|
consumerProguardFiles "proguard-rules.txt"
|
|
101
101
|
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
|
|
102
102
|
buildConfigField "boolean", "IS_ONRAMP_INCLUDED", isOnrampIncluded().toString()
|
|
103
|
+
buildConfigField "String", "STRIPE_ANDROID_SDK_VERSION", "\"${getExtOrDefault("stripeVersion")}\""
|
|
103
104
|
|
|
104
105
|
ndk {
|
|
105
106
|
abiFilters(*reactNativeArchitectures())
|
|
@@ -162,6 +163,7 @@ dependencies {
|
|
|
162
163
|
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.3.1"
|
|
163
164
|
implementation("com.stripe:stripe-android:$stripeVersion")
|
|
164
165
|
implementation("com.stripe:financial-connections:$stripeVersion")
|
|
166
|
+
implementation("com.stripe:payment-method-messaging:$stripeVersion")
|
|
165
167
|
|
|
166
168
|
if (isOnrampIncluded()) {
|
|
167
169
|
implementation("com.stripe:crypto-onramp:$stripeVersion")
|
|
@@ -2,12 +2,38 @@
|
|
|
2
2
|
package="com.reactnativestripesdk">
|
|
3
3
|
|
|
4
4
|
<application>
|
|
5
|
-
<activity
|
|
5
|
+
<activity
|
|
6
6
|
android:name=".CustomPaymentMethodActivity"
|
|
7
7
|
android:theme="@style/Theme.StripeReactNative.Transparent"
|
|
8
8
|
android:exported="false"
|
|
9
9
|
android:launchMode="singleTop"
|
|
10
10
|
android:excludeFromRecents="true"
|
|
11
11
|
android:noHistory="true" />
|
|
12
|
+
|
|
13
|
+
<!-- Interceptor for stripe-connect:// deep links -->
|
|
14
|
+
<activity
|
|
15
|
+
android:name=".StripeConnectDeepLinkInterceptorActivity"
|
|
16
|
+
android:theme="@android:style/Theme.Translucent.NoTitleBar"
|
|
17
|
+
android:exported="true"
|
|
18
|
+
android:launchMode="singleTask"
|
|
19
|
+
android:excludeFromRecents="true"
|
|
20
|
+
android:noHistory="true">
|
|
21
|
+
<intent-filter>
|
|
22
|
+
<action android:name="android.intent.action.VIEW"/>
|
|
23
|
+
<category android:name="android.intent.category.DEFAULT"/>
|
|
24
|
+
<category android:name="android.intent.category.BROWSABLE"/>
|
|
25
|
+
<data android:scheme="stripe-connect"/>
|
|
26
|
+
</intent-filter>
|
|
27
|
+
</activity>
|
|
28
|
+
|
|
29
|
+
<provider
|
|
30
|
+
android:name="androidx.core.content.FileProvider"
|
|
31
|
+
android:authorities="${applicationId}.stripe.fileprovider"
|
|
32
|
+
android:exported="false"
|
|
33
|
+
android:grantUriPermissions="true">
|
|
34
|
+
<meta-data
|
|
35
|
+
android:name="android.support.FILE_PROVIDER_PATHS"
|
|
36
|
+
android:resource="@xml/file_paths" />
|
|
37
|
+
</provider>
|
|
12
38
|
</application>
|
|
13
39
|
</manifest>
|
|
@@ -132,6 +132,7 @@ class EmbeddedPaymentElementViewManager :
|
|
|
132
132
|
map.getMap("billingDetailsCollectionConfiguration"),
|
|
133
133
|
)
|
|
134
134
|
val allowsRemovalOfLastSavedPaymentMethod = map.getBooleanOr("allowsRemovalOfLastSavedPaymentMethod", true)
|
|
135
|
+
val opensCardScannerAutomatically = map.getBooleanOr("opensCardScannerAutomatically", false)
|
|
135
136
|
val primaryButtonLabel = map.getString("primaryButtonLabel")
|
|
136
137
|
val paymentMethodOrder = map.getStringList("paymentMethodOrder")
|
|
137
138
|
|
|
@@ -156,6 +157,7 @@ class EmbeddedPaymentElementViewManager :
|
|
|
156
157
|
?.let { ArrayList(it) },
|
|
157
158
|
),
|
|
158
159
|
).allowsRemovalOfLastSavedPaymentMethod(allowsRemovalOfLastSavedPaymentMethod)
|
|
160
|
+
.opensCardScannerAutomatically(opensCardScannerAutomatically)
|
|
159
161
|
.cardBrandAcceptance(mapToCardBrandAcceptance(map))
|
|
160
162
|
.apply {
|
|
161
163
|
mapToAllowedCardFundingTypes(map)?.let { allowedCardFundingTypes(it) }
|
|
@@ -91,4 +91,12 @@ class EventEmitterCompat(
|
|
|
91
91
|
fun emitOnCustomPaymentMethodConfirmHandlerCallback(value: ReadableMap?) {
|
|
92
92
|
invoke("onCustomPaymentMethodConfirmHandlerCallback", value)
|
|
93
93
|
}
|
|
94
|
+
|
|
95
|
+
fun emitPaymentMethodMessagingElementDidUpdateHeight(value: ReadableMap?) {
|
|
96
|
+
invoke("paymentMethodMessagingElementDidUpdateHeight", value)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
fun emitPaymentMethodMessagingElementConfigureResult(value: ReadableMap?) {
|
|
100
|
+
invoke("paymentMethodMessagingElementConfigureResult", value)
|
|
101
|
+
}
|
|
94
102
|
}
|
|
@@ -7,6 +7,7 @@ import com.reactnativestripesdk.utils.getIntOr
|
|
|
7
7
|
import com.reactnativestripesdk.utils.getLongOr
|
|
8
8
|
import com.reactnativestripesdk.utils.getStringList
|
|
9
9
|
import com.reactnativestripesdk.utils.isEmpty
|
|
10
|
+
import com.stripe.android.model.PaymentMethod
|
|
10
11
|
import com.stripe.android.paymentelement.PaymentMethodOptionsSetupFutureUsagePreview
|
|
11
12
|
import com.stripe.android.paymentsheet.CardFundingFilteringPrivatePreview
|
|
12
13
|
import com.stripe.android.paymentsheet.PaymentSheet
|
|
@@ -77,6 +78,13 @@ private fun mapStringToLinkDisplay(value: String?): PaymentSheet.LinkConfigurati
|
|
|
77
78
|
else -> PaymentSheet.LinkConfiguration.Display.Automatic
|
|
78
79
|
}
|
|
79
80
|
|
|
81
|
+
internal fun computeTermsDisplayForUserKey(publishableKey: String): Map<PaymentMethod.Type, PaymentSheet.TermsDisplay> =
|
|
82
|
+
if (publishableKey.startsWith("uk_")) {
|
|
83
|
+
mapOf(PaymentMethod.Type.Card to PaymentSheet.TermsDisplay.NEVER)
|
|
84
|
+
} else {
|
|
85
|
+
emptyMap()
|
|
86
|
+
}
|
|
87
|
+
|
|
80
88
|
private val mapIntToButtonType =
|
|
81
89
|
mapOf(
|
|
82
90
|
1 to PaymentSheet.GooglePayConfiguration.ButtonType.Buy,
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
@file:OptIn(PaymentMethodMessagingElementPreview::class)
|
|
2
|
+
|
|
3
|
+
package com.reactnativestripesdk
|
|
4
|
+
|
|
5
|
+
import android.content.Context
|
|
6
|
+
import androidx.core.graphics.toColorInt
|
|
7
|
+
import com.facebook.react.bridge.ReadableMap
|
|
8
|
+
import com.facebook.react.bridge.ReadableType
|
|
9
|
+
import com.reactnativestripesdk.utils.PaymentMethodMessagingElementAppearanceException
|
|
10
|
+
import com.reactnativestripesdk.utils.PaymentMethodMessagingElementConfigurationException
|
|
11
|
+
import com.reactnativestripesdk.utils.getDoubleOrNull
|
|
12
|
+
import com.reactnativestripesdk.utils.getStringList
|
|
13
|
+
import com.stripe.android.model.PaymentMethod
|
|
14
|
+
import com.stripe.android.paymentmethodmessaging.element.PaymentMethodMessagingElement
|
|
15
|
+
import com.stripe.android.paymentmethodmessaging.element.PaymentMethodMessagingElementPreview
|
|
16
|
+
|
|
17
|
+
@Throws(PaymentMethodMessagingElementConfigurationException::class)
|
|
18
|
+
fun parseElementConfiguration(map: ReadableMap): PaymentMethodMessagingElement.Configuration {
|
|
19
|
+
val amount =
|
|
20
|
+
map.getDoubleOrNull("amount")?.toLong()
|
|
21
|
+
?: throw PaymentMethodMessagingElementConfigurationException("`amount` is required")
|
|
22
|
+
val currency =
|
|
23
|
+
map.getString("currency")
|
|
24
|
+
?: throw PaymentMethodMessagingElementConfigurationException("`currency` is required")
|
|
25
|
+
|
|
26
|
+
val locale = map.getString("locale")
|
|
27
|
+
val countryCode = map.getString("country")
|
|
28
|
+
val stringPaymentMethodTypes = map.getStringList("paymentMethodTypes")
|
|
29
|
+
val paymentMethodTypes =
|
|
30
|
+
stringPaymentMethodTypes?.mapNotNull {
|
|
31
|
+
PaymentMethod.Type.fromCode(it)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
val config = PaymentMethodMessagingElement.Configuration()
|
|
35
|
+
config.amount(amount)
|
|
36
|
+
config.currency(currency)
|
|
37
|
+
locale?.let { config.locale(it) }
|
|
38
|
+
countryCode?.let { config.countryCode(it) }
|
|
39
|
+
paymentMethodTypes?.let { config.paymentMethodTypes(it) }
|
|
40
|
+
|
|
41
|
+
return config
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
fun parseAppearance(
|
|
45
|
+
map: ReadableMap,
|
|
46
|
+
context: Context,
|
|
47
|
+
): PaymentMethodMessagingElement.Appearance {
|
|
48
|
+
val font =
|
|
49
|
+
map.getMap("font")?.let {
|
|
50
|
+
parseFont(
|
|
51
|
+
it,
|
|
52
|
+
context,
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
val theme = getTheme(map)
|
|
57
|
+
val textColor = dynamicColorFromParams(map, "textColor", theme)
|
|
58
|
+
val linkTextColor = dynamicColorFromParams(map, "linkTextColor", theme)
|
|
59
|
+
val appearance = PaymentMethodMessagingElement.Appearance()
|
|
60
|
+
appearance.theme(theme)
|
|
61
|
+
font?.let { appearance.font(font) }
|
|
62
|
+
val colors = PaymentMethodMessagingElement.Appearance.Colors()
|
|
63
|
+
textColor?.let { colors.textColor(it) }
|
|
64
|
+
linkTextColor?.let { colors.infoIconColor(linkTextColor) }
|
|
65
|
+
appearance.colors(colors)
|
|
66
|
+
|
|
67
|
+
return appearance
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
private fun parseFont(
|
|
71
|
+
map: ReadableMap,
|
|
72
|
+
context: Context,
|
|
73
|
+
): PaymentMethodMessagingElement.Appearance.Font {
|
|
74
|
+
val fontFamily =
|
|
75
|
+
getFontResId(
|
|
76
|
+
map,
|
|
77
|
+
"family",
|
|
78
|
+
context,
|
|
79
|
+
)
|
|
80
|
+
val scaleFactor = map.getDoubleOrNull("scale") ?: 1.0
|
|
81
|
+
val textSize: Double = 16 * scaleFactor
|
|
82
|
+
|
|
83
|
+
val font =
|
|
84
|
+
PaymentMethodMessagingElement.Appearance
|
|
85
|
+
.Font()
|
|
86
|
+
.fontFamily(fontFamily)
|
|
87
|
+
.fontSizeSp(textSize.toFloat())
|
|
88
|
+
|
|
89
|
+
return font
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
private fun getTheme(map: ReadableMap): PaymentMethodMessagingElement.Appearance.Theme {
|
|
93
|
+
val style = map.getString("style")
|
|
94
|
+
return when (style) {
|
|
95
|
+
"dark" -> PaymentMethodMessagingElement.Appearance.Theme.DARK
|
|
96
|
+
"flat" -> PaymentMethodMessagingElement.Appearance.Theme.FLAT
|
|
97
|
+
else -> PaymentMethodMessagingElement.Appearance.Theme.LIGHT
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Parses a ThemedColor from [params] at [key]. Supports both:
|
|
103
|
+
* - Single hex string: "#RRGGBB"
|
|
104
|
+
* - Light/dark object: { "light": "#RRGGBB", "dark": "#RRGGBB" }
|
|
105
|
+
* For light/dark objects, chooses the appropriate color based on current UI mode.
|
|
106
|
+
* Returns null if no color is provided.
|
|
107
|
+
*/
|
|
108
|
+
private fun dynamicColorFromParams(
|
|
109
|
+
params: ReadableMap?,
|
|
110
|
+
key: String,
|
|
111
|
+
theme: PaymentMethodMessagingElement.Appearance.Theme,
|
|
112
|
+
): Int? {
|
|
113
|
+
if (params == null) {
|
|
114
|
+
return null
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// First check if it's a nested map { "light": "#RRGGBB", "dark": "#RRGGBB" }
|
|
118
|
+
if (params.hasKey(key) && params.getType(key) == ReadableType.Map) {
|
|
119
|
+
val colorMap = params.getMap(key)
|
|
120
|
+
val isDark = theme == PaymentMethodMessagingElement.Appearance.Theme.DARK
|
|
121
|
+
|
|
122
|
+
// Pick the hex for current mode, or null
|
|
123
|
+
val hex =
|
|
124
|
+
if (isDark) {
|
|
125
|
+
colorMap?.getString("dark")
|
|
126
|
+
} else {
|
|
127
|
+
colorMap?.getString("light")
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return colorFromHex(hex)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Check if it's a single color string
|
|
134
|
+
return colorFromHex(params.getString(key))
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
@Throws(PaymentMethodMessagingElementAppearanceException::class)
|
|
138
|
+
private fun colorFromHex(hexString: String?): Int? =
|
|
139
|
+
hexString?.trim()?.replace("#", "")?.let {
|
|
140
|
+
if (it.length == 6 || it.length == 8) {
|
|
141
|
+
"#$it".toColorInt()
|
|
142
|
+
} else {
|
|
143
|
+
throw PaymentMethodMessagingElementAppearanceException(
|
|
144
|
+
"Failed to set appearance. Expected hex string of length 6 or 8, but received: $it",
|
|
145
|
+
)
|
|
146
|
+
}
|
|
147
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
package com.reactnativestripesdk
|
|
2
|
+
|
|
3
|
+
import android.annotation.SuppressLint
|
|
4
|
+
import android.app.Application
|
|
5
|
+
import android.content.Context
|
|
6
|
+
import androidx.compose.foundation.layout.Box
|
|
7
|
+
import androidx.compose.runtime.Composable
|
|
8
|
+
import androidx.compose.runtime.LaunchedEffect
|
|
9
|
+
import androidx.compose.runtime.getValue
|
|
10
|
+
import androidx.compose.runtime.mutableStateOf
|
|
11
|
+
import androidx.compose.runtime.remember
|
|
12
|
+
import androidx.compose.runtime.setValue
|
|
13
|
+
import androidx.compose.ui.Modifier
|
|
14
|
+
import androidx.compose.ui.layout.layout
|
|
15
|
+
import androidx.compose.ui.layout.onSizeChanged
|
|
16
|
+
import androidx.compose.ui.platform.LocalDensity
|
|
17
|
+
import androidx.compose.ui.unit.IntOffset
|
|
18
|
+
import androidx.compose.ui.unit.dp
|
|
19
|
+
import com.facebook.react.bridge.Arguments
|
|
20
|
+
import com.facebook.react.uimanager.ThemedReactContext
|
|
21
|
+
import com.stripe.android.paymentmethodmessaging.element.PaymentMethodMessagingElement
|
|
22
|
+
import com.stripe.android.paymentmethodmessaging.element.PaymentMethodMessagingElementPreview
|
|
23
|
+
import kotlinx.coroutines.channels.Channel
|
|
24
|
+
import kotlinx.coroutines.flow.consumeAsFlow
|
|
25
|
+
|
|
26
|
+
@OptIn(PaymentMethodMessagingElementPreview::class)
|
|
27
|
+
class PaymentMethodMessagingElementView(
|
|
28
|
+
context: Context,
|
|
29
|
+
) : StripeAbstractComposeView(context) {
|
|
30
|
+
private sealed interface Event {
|
|
31
|
+
data class Configure(
|
|
32
|
+
val configuration: PaymentMethodMessagingElement.Configuration,
|
|
33
|
+
) : Event
|
|
34
|
+
|
|
35
|
+
data class Appearance(
|
|
36
|
+
val appearance: PaymentMethodMessagingElement.Appearance,
|
|
37
|
+
) : Event
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
var latestElementConfig: PaymentMethodMessagingElement.Configuration? = null
|
|
41
|
+
|
|
42
|
+
private val reactContext get() = context as ThemedReactContext
|
|
43
|
+
private val events = Channel<Event>(Channel.UNLIMITED)
|
|
44
|
+
|
|
45
|
+
@SuppressLint("RestrictedApi")
|
|
46
|
+
@Composable
|
|
47
|
+
override fun Content() {
|
|
48
|
+
val messagingElement =
|
|
49
|
+
remember {
|
|
50
|
+
PaymentMethodMessagingElement.create(context.applicationContext as Application)
|
|
51
|
+
}
|
|
52
|
+
var appearance by remember { mutableStateOf(PaymentMethodMessagingElement.Appearance()) }
|
|
53
|
+
|
|
54
|
+
// collect events: configure, appearance
|
|
55
|
+
LaunchedEffect(Unit) {
|
|
56
|
+
events.consumeAsFlow().collect { ev ->
|
|
57
|
+
when (ev) {
|
|
58
|
+
is Event.Configure -> {
|
|
59
|
+
val loadingPayload = Arguments.createMap()
|
|
60
|
+
loadingPayload.putString("status", "loading")
|
|
61
|
+
requireStripeSdkModule().eventEmitter.emitPaymentMethodMessagingElementConfigureResult(loadingPayload)
|
|
62
|
+
val result =
|
|
63
|
+
messagingElement.configure(
|
|
64
|
+
configuration = ev.configuration,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
val payload = Arguments.createMap()
|
|
68
|
+
when (result) {
|
|
69
|
+
is PaymentMethodMessagingElement.ConfigureResult.Succeeded -> {
|
|
70
|
+
payload.putString("status", "loaded")
|
|
71
|
+
}
|
|
72
|
+
is PaymentMethodMessagingElement.ConfigureResult.NoContent -> {
|
|
73
|
+
payload.putString("status", "no_content")
|
|
74
|
+
reportHeightChange(0f)
|
|
75
|
+
}
|
|
76
|
+
is PaymentMethodMessagingElement.ConfigureResult.Failed -> {
|
|
77
|
+
// send the error back to JS
|
|
78
|
+
val err = result.error
|
|
79
|
+
val msg = err.localizedMessage ?: err.toString()
|
|
80
|
+
// build a RN map
|
|
81
|
+
payload.putString("status", "failed")
|
|
82
|
+
payload.putString("message", msg)
|
|
83
|
+
reportHeightChange(0f)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
requireStripeSdkModule().eventEmitter.emitPaymentMethodMessagingElementConfigureResult(payload)
|
|
87
|
+
}
|
|
88
|
+
is Event.Appearance -> {
|
|
89
|
+
appearance = ev.appearance
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
Box {
|
|
96
|
+
MeasureMessagingElement(
|
|
97
|
+
reportHeightChange = { h -> reportHeightChange(h) },
|
|
98
|
+
) {
|
|
99
|
+
messagingElement.Content(appearance)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
@Composable
|
|
105
|
+
private fun MeasureMessagingElement(
|
|
106
|
+
reportHeightChange: (Float) -> Unit,
|
|
107
|
+
content: @Composable () -> Unit,
|
|
108
|
+
) {
|
|
109
|
+
val density = LocalDensity.current
|
|
110
|
+
var heightDp by remember { mutableStateOf(1.dp) } // non-zero sentinel
|
|
111
|
+
|
|
112
|
+
Box(
|
|
113
|
+
Modifier
|
|
114
|
+
// Post-layout: convert px -> dp, update RN & our dp state
|
|
115
|
+
.onSizeChanged { size ->
|
|
116
|
+
val h = with(density) { size.height.toDp() }
|
|
117
|
+
if (h != heightDp) {
|
|
118
|
+
heightDp = h
|
|
119
|
+
reportHeightChange(h.value) // send dp as Float to RN
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
// Custom measure path: force child to its min intrinsic height (in *px*)
|
|
123
|
+
.layout { measurable, constraints ->
|
|
124
|
+
val widthPx = constraints.maxWidth
|
|
125
|
+
val minHpx = measurable.minIntrinsicHeight(widthPx).coerceAtLeast(1)
|
|
126
|
+
|
|
127
|
+
// Measure the child with a tight height equal to min intrinsic
|
|
128
|
+
val placeable =
|
|
129
|
+
measurable.measure(
|
|
130
|
+
constraints.copy(
|
|
131
|
+
minHeight = minHpx,
|
|
132
|
+
maxHeight = minHpx,
|
|
133
|
+
),
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
// Our own size: use the child’s measured size
|
|
137
|
+
layout(constraints.maxWidth, placeable.height) {
|
|
138
|
+
placeable.placeRelative(IntOffset.Zero)
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
) {
|
|
142
|
+
content()
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
private fun reportHeightChange(height: Float) {
|
|
147
|
+
val params =
|
|
148
|
+
Arguments.createMap().apply {
|
|
149
|
+
putDouble("height", height.toDouble())
|
|
150
|
+
}
|
|
151
|
+
requireStripeSdkModule().eventEmitter.emitPaymentMethodMessagingElementDidUpdateHeight(params)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// APIs
|
|
155
|
+
fun configure(config: PaymentMethodMessagingElement.Configuration) {
|
|
156
|
+
events.trySend(Event.Configure(config))
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
fun appearance(appearance: PaymentMethodMessagingElement.Appearance) {
|
|
160
|
+
events.trySend(Event.Appearance(appearance))
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
private fun requireStripeSdkModule() = requireNotNull(reactContext.getNativeModule(StripeSdkModule::class.java))
|
|
164
|
+
}
|
package/android/src/main/java/com/reactnativestripesdk/PaymentMethodMessagingElementViewManager.kt
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
package com.reactnativestripesdk
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.Dynamic
|
|
4
|
+
import com.facebook.react.module.annotations.ReactModule
|
|
5
|
+
import com.facebook.react.uimanager.ThemedReactContext
|
|
6
|
+
import com.facebook.react.uimanager.ViewGroupManager
|
|
7
|
+
import com.facebook.react.uimanager.annotations.ReactProp
|
|
8
|
+
import com.facebook.react.viewmanagers.PaymentMethodMessagingElementViewManagerDelegate
|
|
9
|
+
import com.facebook.react.viewmanagers.PaymentMethodMessagingElementViewManagerInterface
|
|
10
|
+
import com.reactnativestripesdk.utils.asMapOrNull
|
|
11
|
+
import com.stripe.android.paymentmethodmessaging.element.PaymentMethodMessagingElementPreview
|
|
12
|
+
|
|
13
|
+
@OptIn(PaymentMethodMessagingElementPreview::class)
|
|
14
|
+
@ReactModule(name = PaymentMethodMessagingElementViewManager.NAME)
|
|
15
|
+
class PaymentMethodMessagingElementViewManager :
|
|
16
|
+
ViewGroupManager<PaymentMethodMessagingElementView>(),
|
|
17
|
+
PaymentMethodMessagingElementViewManagerInterface<PaymentMethodMessagingElementView> {
|
|
18
|
+
companion object {
|
|
19
|
+
const val NAME = "PaymentMethodMessagingElementView"
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
private val delegate = PaymentMethodMessagingElementViewManagerDelegate(this)
|
|
23
|
+
|
|
24
|
+
override fun getName() = NAME
|
|
25
|
+
|
|
26
|
+
override fun getDelegate() = delegate
|
|
27
|
+
|
|
28
|
+
override fun createViewInstance(ctx: ThemedReactContext): PaymentMethodMessagingElementView = PaymentMethodMessagingElementView(ctx)
|
|
29
|
+
|
|
30
|
+
override fun onDropViewInstance(view: PaymentMethodMessagingElementView) {
|
|
31
|
+
super.onDropViewInstance(view)
|
|
32
|
+
|
|
33
|
+
view.handleOnDropViewInstance()
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
override fun needsCustomLayoutForChildren(): Boolean = true
|
|
37
|
+
|
|
38
|
+
@ReactProp(name = "appearance")
|
|
39
|
+
override fun setAppearance(
|
|
40
|
+
view: PaymentMethodMessagingElementView?,
|
|
41
|
+
value: Dynamic?,
|
|
42
|
+
) {
|
|
43
|
+
val readableMap = value?.asMapOrNull() ?: return
|
|
44
|
+
view?.let {
|
|
45
|
+
val appearance = parseAppearance(readableMap, view.context)
|
|
46
|
+
view.appearance(appearance)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
@ReactProp(name = "configuration")
|
|
51
|
+
override fun setConfiguration(
|
|
52
|
+
view: PaymentMethodMessagingElementView,
|
|
53
|
+
cfg: Dynamic,
|
|
54
|
+
) {
|
|
55
|
+
val readableMap = cfg.asMapOrNull() ?: return
|
|
56
|
+
|
|
57
|
+
val elementConfig = parseElementConfiguration(readableMap)
|
|
58
|
+
view.latestElementConfig = elementConfig
|
|
59
|
+
view.configure(elementConfig)
|
|
60
|
+
view.post {
|
|
61
|
+
view.requestLayout()
|
|
62
|
+
view.invalidate()
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -39,6 +39,7 @@ import com.reactnativestripesdk.utils.mapFromPaymentMethod
|
|
|
39
39
|
import com.reactnativestripesdk.utils.mapToPreferredNetworks
|
|
40
40
|
import com.reactnativestripesdk.utils.parseCustomPaymentMethods
|
|
41
41
|
import com.stripe.android.ExperimentalAllowsRemovalOfLastSavedPaymentMethodApi
|
|
42
|
+
import com.stripe.android.PaymentConfiguration
|
|
42
43
|
import com.stripe.android.core.reactnative.ReactNativeSdkInternal
|
|
43
44
|
import com.stripe.android.model.PaymentMethod
|
|
44
45
|
import com.stripe.android.paymentelement.ConfirmCustomPaymentMethodCallback
|
|
@@ -106,6 +107,8 @@ class PaymentSheetManager(
|
|
|
106
107
|
val paymentMethodOrder = arguments.getStringList("paymentMethodOrder")
|
|
107
108
|
val allowsRemovalOfLastSavedPaymentMethod =
|
|
108
109
|
arguments.getBooleanOr("allowsRemovalOfLastSavedPaymentMethod", true)
|
|
110
|
+
val opensCardScannerAutomatically =
|
|
111
|
+
arguments.getBooleanOr("opensCardScannerAutomatically", false)
|
|
109
112
|
paymentIntentClientSecret = arguments.getString("paymentIntentClientSecret").orEmpty()
|
|
110
113
|
setupIntentClientSecret = arguments.getString("setupIntentClientSecret").orEmpty()
|
|
111
114
|
intentConfiguration =
|
|
@@ -283,6 +286,7 @@ class PaymentSheetManager(
|
|
|
283
286
|
.preferredNetworks(
|
|
284
287
|
mapToPreferredNetworks(arguments.getIntegerList("preferredNetworks")),
|
|
285
288
|
).allowsRemovalOfLastSavedPaymentMethod(allowsRemovalOfLastSavedPaymentMethod)
|
|
289
|
+
.opensCardScannerAutomatically(opensCardScannerAutomatically)
|
|
286
290
|
.cardBrandAcceptance(mapToCardBrandAcceptance(arguments))
|
|
287
291
|
.apply {
|
|
288
292
|
mapToAllowedCardFundingTypes(arguments)?.let { allowedCardFundingTypes(it) }
|
|
@@ -295,6 +299,14 @@ class PaymentSheetManager(
|
|
|
295
299
|
mapToPaymentMethodLayout(arguments.getString("paymentMethodLayout")),
|
|
296
300
|
)
|
|
297
301
|
|
|
302
|
+
val userKeyTermsDisplay =
|
|
303
|
+
computeTermsDisplayForUserKey(
|
|
304
|
+
PaymentConfiguration.getInstance(context).publishableKey,
|
|
305
|
+
)
|
|
306
|
+
if (userKeyTermsDisplay.isNotEmpty()) {
|
|
307
|
+
configurationBuilder.termsDisplay(userKeyTermsDisplay)
|
|
308
|
+
}
|
|
309
|
+
|
|
298
310
|
paymentSheetConfiguration = configurationBuilder.build()
|
|
299
311
|
|
|
300
312
|
if (arguments.getBooleanOr("customFlow", false)) {
|
|
@@ -430,32 +442,8 @@ class PaymentSheetManager(
|
|
|
430
442
|
|
|
431
443
|
private fun configureFlowController() {
|
|
432
444
|
val onFlowControllerConfigure =
|
|
433
|
-
PaymentSheet.FlowController.ConfigCallback {
|
|
434
|
-
|
|
435
|
-
// Launch async job to convert drawable, but resolve promise synchronously
|
|
436
|
-
CoroutineScope(Dispatchers.Default).launch {
|
|
437
|
-
val imageString =
|
|
438
|
-
try {
|
|
439
|
-
convertDrawableToBase64(paymentOption.icon())
|
|
440
|
-
} catch (e: Exception) {
|
|
441
|
-
val result =
|
|
442
|
-
createError(
|
|
443
|
-
PaymentSheetErrorType.Failed.toString(),
|
|
444
|
-
"Failed to process payment option image: ${e.message}",
|
|
445
|
-
)
|
|
446
|
-
initPromise.resolve(result)
|
|
447
|
-
return@launch
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
val option: WritableMap = Arguments.createMap()
|
|
451
|
-
option.putString("label", paymentOption.label)
|
|
452
|
-
option.putString("image", imageString)
|
|
453
|
-
val result = createResult("paymentOption", option)
|
|
454
|
-
initPromise.resolve(result)
|
|
455
|
-
}
|
|
456
|
-
} ?: run {
|
|
457
|
-
initPromise.resolve(Arguments.createMap())
|
|
458
|
-
}
|
|
445
|
+
PaymentSheet.FlowController.ConfigCallback { success, error ->
|
|
446
|
+
handleFlowControllerConfigured(success, error, initPromise, flowController)
|
|
459
447
|
}
|
|
460
448
|
|
|
461
449
|
if (!paymentIntentClientSecret.isNullOrEmpty()) {
|
|
@@ -717,3 +705,44 @@ internal fun mapToPaymentMethodOptions(options: ReadableMap?): PaymentSheet.Inte
|
|
|
717
705
|
null
|
|
718
706
|
}
|
|
719
707
|
}
|
|
708
|
+
|
|
709
|
+
internal fun handleFlowControllerConfigured(
|
|
710
|
+
success: Boolean,
|
|
711
|
+
error: Throwable?,
|
|
712
|
+
promise: Promise,
|
|
713
|
+
flowController: PaymentSheet.FlowController?,
|
|
714
|
+
) {
|
|
715
|
+
if (!success) {
|
|
716
|
+
promise.resolve(
|
|
717
|
+
createError(
|
|
718
|
+
PaymentSheetErrorType.Failed.toString(),
|
|
719
|
+
error?.message ?: "Failed to configure payment sheet",
|
|
720
|
+
),
|
|
721
|
+
)
|
|
722
|
+
return
|
|
723
|
+
}
|
|
724
|
+
flowController?.getPaymentOption()?.let { paymentOption ->
|
|
725
|
+
CoroutineScope(Dispatchers.Default).launch {
|
|
726
|
+
val imageString =
|
|
727
|
+
try {
|
|
728
|
+
convertDrawableToBase64(paymentOption.icon())
|
|
729
|
+
} catch (e: Exception) {
|
|
730
|
+
val result =
|
|
731
|
+
createError(
|
|
732
|
+
PaymentSheetErrorType.Failed.toString(),
|
|
733
|
+
"Failed to process payment option image: ${e.message}",
|
|
734
|
+
)
|
|
735
|
+
promise.resolve(result)
|
|
736
|
+
return@launch
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
val option: WritableMap = Arguments.createMap()
|
|
740
|
+
option.putString("label", paymentOption.label)
|
|
741
|
+
option.putString("image", imageString)
|
|
742
|
+
val result = createResult("paymentOption", option)
|
|
743
|
+
promise.resolve(result)
|
|
744
|
+
}
|
|
745
|
+
} ?: run {
|
|
746
|
+
promise.resolve(Arguments.createMap())
|
|
747
|
+
}
|
|
748
|
+
}
|