@stripe/stripe-react-native 0.67.0 → 0.68.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 +3 -3
- package/android/gradle.properties +1 -1
- package/android/proguard-rules.txt +4 -0
- package/android/src/main/java/com/reactnativestripesdk/CurrencySelectorAppearance.kt +229 -0
- package/android/src/main/java/com/reactnativestripesdk/CurrencySelectorElementView.kt +8 -1
- package/android/src/main/java/com/reactnativestripesdk/CurrencySelectorElementViewManager.kt +11 -0
- package/android/src/main/java/com/reactnativestripesdk/EmbeddedPaymentElementView.kt +0 -22
- package/android/src/main/java/com/reactnativestripesdk/EmbeddedPaymentElementViewManager.kt +0 -22
- package/android/src/main/java/com/reactnativestripesdk/PaymentSheetManager.kt +11 -4
- package/android/src/main/java/com/reactnativestripesdk/StripeSdkModule.kt +12 -14
- package/android/src/main/java/com/reactnativestripesdk/pushprovisioning/AddToWalletButtonManager.kt +8 -0
- package/android/src/main/java/com/reactnativestripesdk/pushprovisioning/AddToWalletButtonView.kt +6 -0
- package/android/src/main/java/com/reactnativestripesdk/pushprovisioning/PushProvisioningProxy.kt +6 -5
- package/android/src/main/java/com/reactnativestripesdk/pushprovisioning/TapAndPayProxy.kt +91 -0
- package/android/src/oldarch/java/com/facebook/react/viewmanagers/AddToWalletButtonManagerDelegate.java +3 -0
- package/android/src/oldarch/java/com/facebook/react/viewmanagers/AddToWalletButtonManagerInterface.java +1 -0
- package/android/src/oldarch/java/com/facebook/react/viewmanagers/EmbeddedPaymentElementViewManagerDelegate.java +0 -3
- package/android/src/oldarch/java/com/facebook/react/viewmanagers/EmbeddedPaymentElementViewManagerInterface.java +0 -1
- package/android/src/oldarch/java/com/facebook/react/viewmanagers/StripeCurrencySelectorElementManagerDelegate.java +4 -0
- package/android/src/oldarch/java/com/facebook/react/viewmanagers/StripeCurrencySelectorElementManagerInterface.java +2 -0
- package/android/src/oldarch/java/com/reactnativestripesdk/NativeStripeSdkModuleSpec.java +0 -4
- package/android/src/onramp/java/com/reactnativestripesdk/OnrampSdkModule.kt +7 -7
- package/android/src/test/java/com/reactnativestripesdk/CurrencySelectorAppearanceTest.kt +137 -0
- package/ios/CurrencySelectorAppearance.swift +120 -0
- package/ios/CurrencySelectorElementManager.m +1 -0
- package/ios/CurrencySelectorElementView.swift +16 -1
- package/ios/NewArch/EmbeddedPaymentElementViewComponentView.mm +0 -4
- package/ios/NewArch/StripeCurrencySelectorElementComponentView.mm +1 -0
- package/ios/StripeSdk.mm +0 -7
- package/ios/StripeSdkImpl+Embedded.swift +0 -24
- package/ios/StripeSdkImpl+PaymentSheet.swift +1 -2
- package/lib/commonjs/components/AddToWalletButton.js.map +1 -1
- package/lib/commonjs/components/CurrencySelectorElement.js +1 -1
- package/lib/commonjs/components/CurrencySelectorElement.js.map +1 -1
- package/lib/commonjs/connect/EmbeddedComponent.js +2 -2
- package/lib/commonjs/connect/EmbeddedComponent.js.map +1 -1
- package/lib/commonjs/specs/NativeAddToWalletButton.js +1 -1
- package/lib/commonjs/specs/NativeAddToWalletButton.js.map +1 -1
- package/lib/commonjs/specs/NativeCurrencySelectorElement.js +1 -1
- package/lib/commonjs/specs/NativeCurrencySelectorElement.js.map +1 -1
- package/lib/commonjs/specs/NativeEmbeddedPaymentElement.js +1 -1
- package/lib/commonjs/specs/NativeEmbeddedPaymentElement.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/PushProvisioning.js.map +1 -1
- package/lib/module/components/AddToWalletButton.js.map +1 -1
- package/lib/module/components/CurrencySelectorElement.js +1 -1
- package/lib/module/components/CurrencySelectorElement.js.map +1 -1
- package/lib/module/connect/EmbeddedComponent.js +2 -2
- package/lib/module/connect/EmbeddedComponent.js.map +1 -1
- package/lib/module/specs/NativeAddToWalletButton.js +1 -1
- package/lib/module/specs/NativeAddToWalletButton.js.map +1 -1
- package/lib/module/specs/NativeCurrencySelectorElement.js +1 -1
- package/lib/module/specs/NativeCurrencySelectorElement.js.map +1 -1
- package/lib/module/specs/NativeEmbeddedPaymentElement.js +1 -1
- package/lib/module/specs/NativeEmbeddedPaymentElement.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/PushProvisioning.js.map +1 -1
- package/lib/typescript/src/components/AddToWalletButton.d.ts +2 -0
- package/lib/typescript/src/components/AddToWalletButton.d.ts.map +1 -1
- package/lib/typescript/src/components/CurrencySelectorElement.d.ts +76 -1
- package/lib/typescript/src/components/CurrencySelectorElement.d.ts.map +1 -1
- package/lib/typescript/src/connect/EmbeddedComponent.d.ts +23 -0
- package/lib/typescript/src/connect/EmbeddedComponent.d.ts.map +1 -1
- package/lib/typescript/src/specs/NativeAddToWalletButton.d.ts +1 -0
- package/lib/typescript/src/specs/NativeAddToWalletButton.d.ts.map +1 -1
- package/lib/typescript/src/specs/NativeCurrencySelectorElement.d.ts +4 -0
- package/lib/typescript/src/specs/NativeCurrencySelectorElement.d.ts.map +1 -1
- package/lib/typescript/src/specs/NativeEmbeddedPaymentElement.d.ts +0 -1
- package/lib/typescript/src/specs/NativeEmbeddedPaymentElement.d.ts.map +1 -1
- package/lib/typescript/src/specs/NativeStripeSdkModule.d.ts +0 -1
- package/lib/typescript/src/specs/NativeStripeSdkModule.d.ts.map +1 -1
- package/lib/typescript/src/types/EmbeddedPaymentElement.d.ts +1 -10
- package/lib/typescript/src/types/EmbeddedPaymentElement.d.ts.map +1 -1
- package/lib/typescript/src/types/PaymentMethod.d.ts +2 -0
- package/lib/typescript/src/types/PaymentMethod.d.ts.map +1 -1
- package/lib/typescript/src/types/PushProvisioning.d.ts +2 -0
- package/lib/typescript/src/types/PushProvisioning.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/AddToWalletButton.tsx +2 -0
- package/src/components/CurrencySelectorElement.tsx +101 -1
- package/src/connect/EmbeddedComponent.tsx +47 -10
- package/src/specs/NativeAddToWalletButton.ts +1 -0
- package/src/specs/NativeCurrencySelectorElement.ts +5 -0
- package/src/specs/NativeEmbeddedPaymentElement.ts +1 -10
- package/src/specs/NativeStripeSdkModule.ts +0 -3
- package/src/types/EmbeddedPaymentElement.tsx +4 -27
- package/src/types/PaymentMethod.ts +2 -0
- package/src/types/PushProvisioning.ts +2 -0
- package/stripe-react-native.podspec +1 -1
package/android/build.gradle
CHANGED
|
@@ -164,11 +164,11 @@ dependencies {
|
|
|
164
164
|
implementation("com.stripe:stripe-android:$stripeVersion")
|
|
165
165
|
implementation("com.stripe:financial-connections:$stripeVersion")
|
|
166
166
|
implementation("com.stripe:payment-method-messaging:$stripeVersion")
|
|
167
|
-
|
|
167
|
+
|
|
168
168
|
if (isOnrampIncluded()) {
|
|
169
169
|
implementation("com.stripe:crypto-onramp:$stripeVersion")
|
|
170
170
|
}
|
|
171
|
-
|
|
171
|
+
|
|
172
172
|
implementation "com.google.android.material:material:1.3.0"
|
|
173
173
|
implementation "androidx.appcompat:appcompat:1.4.1"
|
|
174
174
|
implementation "androidx.legacy:legacy-support-v4:1.0.0"
|
|
@@ -179,7 +179,7 @@ dependencies {
|
|
|
179
179
|
compileOnly "com.google.android.gms:play-services-wallet:19.3.0"
|
|
180
180
|
|
|
181
181
|
// Users need to declare this dependency on their own, otherwise all methods are a no-op
|
|
182
|
-
compileOnly "com.stripe:stripe-android-issuing-push-provisioning:1.
|
|
182
|
+
compileOnly "com.stripe:stripe-android-issuing-push-provisioning:1.3.0"
|
|
183
183
|
|
|
184
184
|
testImplementation "junit:junit:4.13"
|
|
185
185
|
testImplementation "org.mockito:mockito-core:3.+"
|
|
@@ -3,4 +3,4 @@ StripeSdk_compileSdkVersion=36
|
|
|
3
3
|
StripeSdk_targetSdkVersion=36
|
|
4
4
|
StripeSdk_minSdkVersion=23
|
|
5
5
|
# Keep StripeSdk_stripeVersion in sync with https://github.com/stripe/stripe-identity-react-native/blob/main/android/gradle.properties
|
|
6
|
-
StripeSdk_stripeVersion=23.
|
|
6
|
+
StripeSdk_stripeVersion=23.11.0
|
|
@@ -5,3 +5,7 @@
|
|
|
5
5
|
-keepclassmembers class com.stripe.android.pushProvisioning.** {
|
|
6
6
|
public *;
|
|
7
7
|
}
|
|
8
|
+
|
|
9
|
+
# play-services-tapandpay is a private Google SDK that must be provided by the app.
|
|
10
|
+
# Suppress R8 warnings when it is absent (e.g. in the example app build).
|
|
11
|
+
-dontwarn com.google.android.gms.tapandpay.**
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
package com.reactnativestripesdk
|
|
2
|
+
|
|
3
|
+
import android.annotation.SuppressLint
|
|
4
|
+
import android.content.Context
|
|
5
|
+
import android.content.res.Configuration
|
|
6
|
+
import androidx.compose.ui.graphics.Color
|
|
7
|
+
import androidx.core.graphics.toColorInt
|
|
8
|
+
import com.facebook.react.bridge.ReadableMap
|
|
9
|
+
import com.facebook.react.bridge.ReadableType
|
|
10
|
+
import com.reactnativestripesdk.utils.PaymentSheetAppearanceException
|
|
11
|
+
import com.stripe.android.checkout.Checkout
|
|
12
|
+
import com.stripe.android.paymentelement.CheckoutSessionPreview
|
|
13
|
+
|
|
14
|
+
@OptIn(CheckoutSessionPreview::class)
|
|
15
|
+
@SuppressLint("RestrictedApi")
|
|
16
|
+
fun buildCurrencySelectorAppearance(
|
|
17
|
+
params: ReadableMap?,
|
|
18
|
+
context: Context,
|
|
19
|
+
): Checkout.CurrencySelectorContentAppearance {
|
|
20
|
+
val appearance = Checkout.CurrencySelectorContentAppearance()
|
|
21
|
+
|
|
22
|
+
params ?: return appearance
|
|
23
|
+
|
|
24
|
+
applyCurrencySelectorColors(appearance, params, context)
|
|
25
|
+
applyCurrencySelectorDimensions(appearance, params)
|
|
26
|
+
applyCurrencySelectorFont(appearance, params, context)
|
|
27
|
+
applyCurrencySelectorLabelContent(appearance, params)
|
|
28
|
+
|
|
29
|
+
return appearance
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@OptIn(CheckoutSessionPreview::class)
|
|
33
|
+
@SuppressLint("RestrictedApi")
|
|
34
|
+
private fun applyCurrencySelectorColors(
|
|
35
|
+
appearance: Checkout.CurrencySelectorContentAppearance,
|
|
36
|
+
params: ReadableMap,
|
|
37
|
+
context: Context,
|
|
38
|
+
) {
|
|
39
|
+
dynamicColorFromParams(context, params, CurrencySelectorAppearanceKeys.BACKGROUND)?.let {
|
|
40
|
+
appearance.background(it)
|
|
41
|
+
}
|
|
42
|
+
dynamicColorFromParams(context, params, CurrencySelectorAppearanceKeys.SELECTED_BACKGROUND)?.let {
|
|
43
|
+
appearance.selectedBackground(it)
|
|
44
|
+
}
|
|
45
|
+
dynamicColorFromParams(context, params, CurrencySelectorAppearanceKeys.BORDER_COLOR)?.let {
|
|
46
|
+
appearance.borderColor(it)
|
|
47
|
+
}
|
|
48
|
+
dynamicColorFromParams(context, params, CurrencySelectorAppearanceKeys.TEXT_COLOR)?.let {
|
|
49
|
+
appearance.textColor(it)
|
|
50
|
+
}
|
|
51
|
+
dynamicColorFromParams(context, params, CurrencySelectorAppearanceKeys.SELECTED_TEXT_COLOR)?.let {
|
|
52
|
+
appearance.selectedTextColor(it)
|
|
53
|
+
}
|
|
54
|
+
dynamicColorFromParams(context, params, CurrencySelectorAppearanceKeys.TEXT_SECONDARY_COLOR)?.let {
|
|
55
|
+
appearance.textSecondaryColor(it)
|
|
56
|
+
}
|
|
57
|
+
dynamicColorFromParams(context, params, CurrencySelectorAppearanceKeys.DANGER_COLOR)?.let {
|
|
58
|
+
appearance.dangerColor(it)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
@OptIn(CheckoutSessionPreview::class)
|
|
63
|
+
@SuppressLint("RestrictedApi")
|
|
64
|
+
private fun applyCurrencySelectorDimensions(
|
|
65
|
+
appearance: Checkout.CurrencySelectorContentAppearance,
|
|
66
|
+
params: ReadableMap,
|
|
67
|
+
) {
|
|
68
|
+
getFloatOrNull(params, CurrencySelectorAppearanceKeys.CORNER_RADIUS)?.let {
|
|
69
|
+
appearance.cornerRadiusDp(it)
|
|
70
|
+
}
|
|
71
|
+
getFloatOrNull(params, CurrencySelectorAppearanceKeys.BORDER_WIDTH)?.let {
|
|
72
|
+
appearance.borderWidthDp(it)
|
|
73
|
+
}
|
|
74
|
+
getFloatOrNull(params, CurrencySelectorAppearanceKeys.CONTENT_VERTICAL_PADDING)?.let {
|
|
75
|
+
appearance.contentVerticalPaddingDp(it)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
@OptIn(CheckoutSessionPreview::class)
|
|
80
|
+
@SuppressLint("RestrictedApi")
|
|
81
|
+
private fun applyCurrencySelectorFont(
|
|
82
|
+
appearance: Checkout.CurrencySelectorContentAppearance,
|
|
83
|
+
params: ReadableMap,
|
|
84
|
+
context: Context,
|
|
85
|
+
) {
|
|
86
|
+
params.getMap(CurrencySelectorAppearanceKeys.FONT)?.let { font ->
|
|
87
|
+
getCurrencySelectorFontResId(font, CurrencySelectorAppearanceKeys.FAMILY, context)?.let {
|
|
88
|
+
appearance.fontResId(it)
|
|
89
|
+
}
|
|
90
|
+
getFloatOrNull(font, CurrencySelectorAppearanceKeys.SCALE)?.let {
|
|
91
|
+
appearance.sizeScaleFactor(it)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
@OptIn(CheckoutSessionPreview::class)
|
|
97
|
+
@SuppressLint("RestrictedApi")
|
|
98
|
+
private fun applyCurrencySelectorLabelContent(
|
|
99
|
+
appearance: Checkout.CurrencySelectorContentAppearance,
|
|
100
|
+
params: ReadableMap,
|
|
101
|
+
) {
|
|
102
|
+
when (params.getString(CurrencySelectorAppearanceKeys.LABEL_CONTENT)) {
|
|
103
|
+
CurrencySelectorAppearanceKeys.AMOUNT -> {
|
|
104
|
+
appearance.labelContent(Checkout.CurrencySelectorContentAppearance.LabelContent.AMOUNT)
|
|
105
|
+
}
|
|
106
|
+
CurrencySelectorAppearanceKeys.CURRENCY_CODE -> {
|
|
107
|
+
appearance.labelContent(Checkout.CurrencySelectorContentAppearance.LabelContent.CURRENCY_CODE)
|
|
108
|
+
}
|
|
109
|
+
CurrencySelectorAppearanceKeys.AUTOMATIC -> {
|
|
110
|
+
appearance.labelContent(Checkout.CurrencySelectorContentAppearance.LabelContent.AUTOMATIC)
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private fun dynamicColorFromParams(
|
|
116
|
+
context: Context,
|
|
117
|
+
params: ReadableMap?,
|
|
118
|
+
key: String,
|
|
119
|
+
): Color? {
|
|
120
|
+
if (params == null) {
|
|
121
|
+
return null
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (params.hasKey(key) && params.getType(key) == ReadableType.Map) {
|
|
125
|
+
val colorMap = params.getMap(key)
|
|
126
|
+
val isDark =
|
|
127
|
+
(
|
|
128
|
+
context.resources.configuration.uiMode
|
|
129
|
+
and Configuration.UI_MODE_NIGHT_MASK
|
|
130
|
+
) == Configuration.UI_MODE_NIGHT_YES
|
|
131
|
+
|
|
132
|
+
val hex =
|
|
133
|
+
if (isDark) {
|
|
134
|
+
colorMap?.getString(CurrencySelectorAppearanceKeys.DARK)
|
|
135
|
+
} else {
|
|
136
|
+
colorMap?.getString(CurrencySelectorAppearanceKeys.LIGHT)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return colorFromHex(hex)?.let { Color(it) }
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return colorFromHex(params.getString(key))?.let { Color(it) }
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
private fun colorFromHex(hexString: String?): Int? =
|
|
146
|
+
hexString?.trim()?.replace("#", "")?.let {
|
|
147
|
+
if (it.length == HEX_COLOR_LENGTH_RGB || it.length == HEX_COLOR_LENGTH_ARGB) {
|
|
148
|
+
"#$it".toColorInt()
|
|
149
|
+
} else {
|
|
150
|
+
android.util.Log.w(
|
|
151
|
+
"StripeCurrencySelector",
|
|
152
|
+
"Invalid hex color: expected length 6 or 8, but received: $it",
|
|
153
|
+
)
|
|
154
|
+
null
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
private fun getFloatOrNull(
|
|
159
|
+
map: ReadableMap?,
|
|
160
|
+
key: String,
|
|
161
|
+
): Float? =
|
|
162
|
+
if (map?.hasKey(key) == true && map.getType(key) == ReadableType.Number) {
|
|
163
|
+
map.getDouble(key).toFloat()
|
|
164
|
+
} else {
|
|
165
|
+
null
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
@SuppressLint("DiscouragedApi")
|
|
169
|
+
private fun getCurrencySelectorFontResId(
|
|
170
|
+
map: ReadableMap?,
|
|
171
|
+
key: String,
|
|
172
|
+
context: Context,
|
|
173
|
+
): Int? {
|
|
174
|
+
val fontErrorPrefix = "Encountered an error when setting a custom font:"
|
|
175
|
+
if (map?.hasKey(key) != true) {
|
|
176
|
+
return null
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
val fontFileName = map.getString(key)
|
|
180
|
+
val validationError =
|
|
181
|
+
when {
|
|
182
|
+
fontFileName == null ->
|
|
183
|
+
"$fontErrorPrefix expected String for font.$key, but received null."
|
|
184
|
+
Regex("[^a-z0-9_]").containsMatchIn(fontFileName) ->
|
|
185
|
+
"$fontErrorPrefix appearance.font.$key should only contain " +
|
|
186
|
+
"lowercase alphanumeric characters and underscores on Android, " +
|
|
187
|
+
"but received '$fontFileName'. " +
|
|
188
|
+
"This value must match the filename in android/app/src/main/res/font"
|
|
189
|
+
else -> null
|
|
190
|
+
}
|
|
191
|
+
if (validationError != null) {
|
|
192
|
+
throw PaymentSheetAppearanceException(validationError)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
val id = context.resources.getIdentifier(fontFileName, "font", context.packageName)
|
|
196
|
+
if (id == 0) {
|
|
197
|
+
throw PaymentSheetAppearanceException("$fontErrorPrefix Failed to find font: $fontFileName")
|
|
198
|
+
}
|
|
199
|
+
return id
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
private object CurrencySelectorAppearanceKeys {
|
|
203
|
+
const val LIGHT = "light"
|
|
204
|
+
const val DARK = "dark"
|
|
205
|
+
|
|
206
|
+
const val BACKGROUND = "background"
|
|
207
|
+
const val SELECTED_BACKGROUND = "selectedBackground"
|
|
208
|
+
const val BORDER_COLOR = "borderColor"
|
|
209
|
+
const val TEXT_COLOR = "textColor"
|
|
210
|
+
const val SELECTED_TEXT_COLOR = "selectedTextColor"
|
|
211
|
+
const val TEXT_SECONDARY_COLOR = "textSecondaryColor"
|
|
212
|
+
const val DANGER_COLOR = "dangerColor"
|
|
213
|
+
|
|
214
|
+
const val CORNER_RADIUS = "cornerRadius"
|
|
215
|
+
const val BORDER_WIDTH = "borderWidth"
|
|
216
|
+
const val CONTENT_VERTICAL_PADDING = "contentVerticalPadding"
|
|
217
|
+
|
|
218
|
+
const val FONT = "font"
|
|
219
|
+
const val FAMILY = "family"
|
|
220
|
+
const val SCALE = "scale"
|
|
221
|
+
|
|
222
|
+
const val LABEL_CONTENT = "labelContent"
|
|
223
|
+
const val AUTOMATIC = "automatic"
|
|
224
|
+
const val CURRENCY_CODE = "currencyCode"
|
|
225
|
+
const val AMOUNT = "amount"
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
private const val HEX_COLOR_LENGTH_RGB = 6
|
|
229
|
+
private const val HEX_COLOR_LENGTH_ARGB = 8
|
|
@@ -20,6 +20,7 @@ import com.facebook.react.bridge.Arguments
|
|
|
20
20
|
import com.facebook.react.bridge.ReactContext
|
|
21
21
|
import com.facebook.react.bridge.WritableMap
|
|
22
22
|
import com.facebook.react.uimanager.UIManagerHelper
|
|
23
|
+
import com.stripe.android.checkout.Checkout
|
|
23
24
|
import com.stripe.android.paymentelement.CheckoutSessionPreview
|
|
24
25
|
|
|
25
26
|
@OptIn(CheckoutSessionPreview::class)
|
|
@@ -31,6 +32,7 @@ class CurrencySelectorElementView(
|
|
|
31
32
|
// Backing state for props. mutableStateOf so Compose recomposes on changes.
|
|
32
33
|
private var sessionKeyState = mutableStateOf<String?>(null)
|
|
33
34
|
private var disabledState = mutableStateOf(false)
|
|
35
|
+
private var appearanceState = mutableStateOf(Checkout.CurrencySelectorContentAppearance())
|
|
34
36
|
|
|
35
37
|
fun setSessionKey(value: String?) {
|
|
36
38
|
sessionKeyState.value = value?.takeIf { it.isNotEmpty() }
|
|
@@ -40,11 +42,16 @@ class CurrencySelectorElementView(
|
|
|
40
42
|
disabledState.value = value
|
|
41
43
|
}
|
|
42
44
|
|
|
45
|
+
fun setAppearance(value: Checkout.CurrencySelectorContentAppearance) {
|
|
46
|
+
appearanceState.value = value
|
|
47
|
+
}
|
|
48
|
+
|
|
43
49
|
@SuppressLint("RestrictedApi")
|
|
44
50
|
@Composable
|
|
45
51
|
override fun Content() {
|
|
46
52
|
val sessionKey by remember { sessionKeyState }
|
|
47
53
|
val disabled by remember { disabledState }
|
|
54
|
+
val appearance by remember { appearanceState }
|
|
48
55
|
|
|
49
56
|
val checkout = remember(sessionKey) {
|
|
50
57
|
sessionKey?.let { key ->
|
|
@@ -63,7 +70,7 @@ class CurrencySelectorElementView(
|
|
|
63
70
|
reportHeightChange = { reportHeightChange(it) },
|
|
64
71
|
) {
|
|
65
72
|
Box {
|
|
66
|
-
checkout.CurrencySelectorContent()
|
|
73
|
+
checkout.CurrencySelectorContent(appearance = appearance)
|
|
67
74
|
if (disabled) {
|
|
68
75
|
// The Stripe Android composable doesn't yet expose an `isEnabled`
|
|
69
76
|
// parameter, so swallow taps with a transparent pointer-input
|
package/android/src/main/java/com/reactnativestripesdk/CurrencySelectorElementViewManager.kt
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
package com.reactnativestripesdk
|
|
2
2
|
|
|
3
|
+
import com.facebook.react.bridge.Dynamic
|
|
4
|
+
import com.facebook.react.bridge.ReadableType
|
|
3
5
|
import com.facebook.react.module.annotations.ReactModule
|
|
4
6
|
import com.facebook.react.uimanager.ThemedReactContext
|
|
5
7
|
import com.facebook.react.uimanager.ViewGroupManager
|
|
@@ -56,4 +58,13 @@ class CurrencySelectorElementViewManager :
|
|
|
56
58
|
) {
|
|
57
59
|
view.setDisabled(value)
|
|
58
60
|
}
|
|
61
|
+
|
|
62
|
+
@ReactProp(name = "appearance")
|
|
63
|
+
override fun setAppearance(
|
|
64
|
+
view: CurrencySelectorElementView,
|
|
65
|
+
value: Dynamic?,
|
|
66
|
+
) {
|
|
67
|
+
val appearanceParams = if (value?.type == ReadableType.Map) value.asMap() else null
|
|
68
|
+
view.setAppearance(buildCurrencySelectorAppearance(appearanceParams, view.context))
|
|
69
|
+
}
|
|
59
70
|
}
|
|
@@ -64,10 +64,6 @@ class EmbeddedPaymentElementView(
|
|
|
64
64
|
val intentConfiguration: PaymentSheet.IntentConfiguration,
|
|
65
65
|
) : Event
|
|
66
66
|
|
|
67
|
-
data class UpdateWithCheckout(
|
|
68
|
-
val checkout: Checkout,
|
|
69
|
-
) : Event
|
|
70
|
-
|
|
71
67
|
data object Confirm : Event
|
|
72
68
|
|
|
73
69
|
data object ClearPaymentOption : Event
|
|
@@ -347,20 +343,6 @@ class EmbeddedPaymentElementView(
|
|
|
347
343
|
latestIntentConfig = ev.intentConfiguration
|
|
348
344
|
}
|
|
349
345
|
|
|
350
|
-
is Event.UpdateWithCheckout -> {
|
|
351
|
-
val elemConfig = latestElementConfig ?: return@collect emitUpdateMissingConfiguration()
|
|
352
|
-
|
|
353
|
-
val result =
|
|
354
|
-
embedded.configure(
|
|
355
|
-
checkout = ev.checkout,
|
|
356
|
-
configuration = elemConfig,
|
|
357
|
-
)
|
|
358
|
-
|
|
359
|
-
handleUpdateResult(result)
|
|
360
|
-
|
|
361
|
-
latestCheckout = ev.checkout
|
|
362
|
-
}
|
|
363
|
-
|
|
364
346
|
is Event.Confirm -> {
|
|
365
347
|
embedded.confirm()
|
|
366
348
|
}
|
|
@@ -506,10 +488,6 @@ class EmbeddedPaymentElementView(
|
|
|
506
488
|
events.trySend(Event.Update(intentConfig))
|
|
507
489
|
}
|
|
508
490
|
|
|
509
|
-
fun updateWithCheckout(checkout: Checkout) {
|
|
510
|
-
events.trySend(Event.UpdateWithCheckout(checkout))
|
|
511
|
-
}
|
|
512
|
-
|
|
513
491
|
fun confirm() {
|
|
514
492
|
events.trySend(Event.Confirm)
|
|
515
493
|
}
|
|
@@ -244,28 +244,6 @@ class EmbeddedPaymentElementViewManager :
|
|
|
244
244
|
}
|
|
245
245
|
}
|
|
246
246
|
|
|
247
|
-
override fun updateWithCheckout(
|
|
248
|
-
view: EmbeddedPaymentElementView,
|
|
249
|
-
sessionKey: String?,
|
|
250
|
-
) {
|
|
251
|
-
if (sessionKey == null) return
|
|
252
|
-
|
|
253
|
-
val stripeSdkModule =
|
|
254
|
-
(view.context as ThemedReactContext).getNativeModule(StripeSdkModule::class.java)
|
|
255
|
-
val checkout = stripeSdkModule?.checkoutInstances?.get(sessionKey)
|
|
256
|
-
if (checkout == null) {
|
|
257
|
-
val payload =
|
|
258
|
-
Arguments.createMap().apply {
|
|
259
|
-
putString("message", "Checkout session not found.")
|
|
260
|
-
}
|
|
261
|
-
stripeSdkModule?.eventEmitter?.emitEmbeddedPaymentElementLoadingFailed(payload)
|
|
262
|
-
stripeSdkModule?.eventEmitter?.emitEmbeddedPaymentElementUpdateComplete(null)
|
|
263
|
-
return
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
view.updateWithCheckout(checkout)
|
|
267
|
-
}
|
|
268
|
-
|
|
269
247
|
private fun jsonToWritableMap(json: JSONObject): WritableMap {
|
|
270
248
|
val map = Arguments.createMap()
|
|
271
249
|
val keys = json.keys()
|
|
@@ -239,6 +239,16 @@ class PaymentSheetManager(
|
|
|
239
239
|
return
|
|
240
240
|
}
|
|
241
241
|
|
|
242
|
+
if (checkoutSessionKey != null) {
|
|
243
|
+
promise.resolve(
|
|
244
|
+
createError(
|
|
245
|
+
ErrorType.Failed.toString(),
|
|
246
|
+
"PaymentSheet with checkout is not supported. Use customFlow: true instead.",
|
|
247
|
+
),
|
|
248
|
+
)
|
|
249
|
+
return
|
|
250
|
+
}
|
|
251
|
+
|
|
242
252
|
lastConfigureWasCustomFlow = false
|
|
243
253
|
if (paymentSheet == null) {
|
|
244
254
|
initPaymentSheet(args, promise)
|
|
@@ -437,10 +447,7 @@ class PaymentSheetManager(
|
|
|
437
447
|
override fun onPresent() {
|
|
438
448
|
keepJsAwake = KeepJsAwakeTask(context).apply { start() }
|
|
439
449
|
if (lastConfigureWasCustomFlow == false) {
|
|
440
|
-
|
|
441
|
-
if (checkout != null) {
|
|
442
|
-
paymentSheet?.presentWithCheckout(checkout, paymentSheetConfiguration)
|
|
443
|
-
} else if (!paymentIntentClientSecret.isNullOrEmpty()) {
|
|
450
|
+
if (!paymentIntentClientSecret.isNullOrEmpty()) {
|
|
444
451
|
paymentSheet?.presentWithPaymentIntent(
|
|
445
452
|
paymentIntentClientSecret!!,
|
|
446
453
|
paymentSheetConfiguration,
|
|
@@ -27,6 +27,7 @@ import com.facebook.react.modules.systeminfo.ReactNativeVersion
|
|
|
27
27
|
import com.reactnativestripesdk.addresssheet.AddressLauncherManager
|
|
28
28
|
import com.reactnativestripesdk.customersheet.CustomerSheetManager
|
|
29
29
|
import com.reactnativestripesdk.pushprovisioning.PushProvisioningProxy
|
|
30
|
+
import com.reactnativestripesdk.pushprovisioning.TapAndPayProxy
|
|
30
31
|
import com.reactnativestripesdk.utils.ConfirmPaymentErrorType
|
|
31
32
|
import com.reactnativestripesdk.utils.CreateTokenErrorType
|
|
32
33
|
import com.reactnativestripesdk.utils.DefaultActivityLifecycleCallbacks
|
|
@@ -955,6 +956,12 @@ class StripeSdkModule(
|
|
|
955
956
|
return
|
|
956
957
|
}
|
|
957
958
|
|
|
959
|
+
val cardBrand =
|
|
960
|
+
getValOr(params, "cardBrand", null) ?: run {
|
|
961
|
+
promise.resolve(createError("Failed", "You must provide cardBrand"))
|
|
962
|
+
return
|
|
963
|
+
}
|
|
964
|
+
|
|
958
965
|
if (params.getBooleanOr("supportsTapToPay", true) &&
|
|
959
966
|
!PushProvisioningProxy.isNFCEnabled(
|
|
960
967
|
reactApplicationContext,
|
|
@@ -964,14 +971,14 @@ class StripeSdkModule(
|
|
|
964
971
|
return
|
|
965
972
|
}
|
|
966
973
|
|
|
967
|
-
getCurrentActivityOrResolveWithError(promise)?.let {
|
|
968
|
-
|
|
974
|
+
getCurrentActivityOrResolveWithError(promise)?.let { activity ->
|
|
975
|
+
TapAndPayProxy.checkEligibility(activity, last4, cardBrand) { canAdd, token, error ->
|
|
969
976
|
val result =
|
|
970
977
|
error?.let {
|
|
971
|
-
createCanAddCardResult(false, "MISSING_CONFIGURATION"
|
|
978
|
+
createCanAddCardResult(false, "MISSING_CONFIGURATION")
|
|
972
979
|
} ?: run {
|
|
973
|
-
val status = if (
|
|
974
|
-
createCanAddCardResult(
|
|
980
|
+
val status = if (!canAdd) "CARD_ALREADY_EXISTS" else null
|
|
981
|
+
createCanAddCardResult(canAdd, status, token)
|
|
975
982
|
}
|
|
976
983
|
promise.resolve(result)
|
|
977
984
|
}
|
|
@@ -1443,15 +1450,6 @@ class StripeSdkModule(
|
|
|
1443
1450
|
// TODO:
|
|
1444
1451
|
}
|
|
1445
1452
|
|
|
1446
|
-
@ReactMethod
|
|
1447
|
-
override fun updateEmbeddedPaymentElementWithCheckout(
|
|
1448
|
-
sessionKey: String,
|
|
1449
|
-
promise: Promise,
|
|
1450
|
-
) {
|
|
1451
|
-
// No-op on Android. JS dispatches Checkout updates through the view command instead.
|
|
1452
|
-
promise.resolve(null)
|
|
1453
|
-
}
|
|
1454
|
-
|
|
1455
1453
|
@ReactMethod
|
|
1456
1454
|
override fun clearEmbeddedPaymentOption(
|
|
1457
1455
|
viewTag: Double,
|
package/android/src/main/java/com/reactnativestripesdk/pushprovisioning/AddToWalletButtonManager.kt
CHANGED
|
@@ -83,6 +83,14 @@ class AddToWalletButtonManager :
|
|
|
83
83
|
// noop, iOS only.
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
@ReactProp(name = "isBounceProvisioned")
|
|
87
|
+
override fun setIsBounceProvisioned(
|
|
88
|
+
view: AddToWalletButtonView,
|
|
89
|
+
value: Boolean,
|
|
90
|
+
) {
|
|
91
|
+
view.setIsBounceProvisioned(value)
|
|
92
|
+
}
|
|
93
|
+
|
|
86
94
|
@ReactProp(name = "testEnv")
|
|
87
95
|
override fun setTestEnv(
|
|
88
96
|
view: AddToWalletButtonView,
|
package/android/src/main/java/com/reactnativestripesdk/pushprovisioning/AddToWalletButtonView.kt
CHANGED
|
@@ -33,6 +33,7 @@ class AddToWalletButtonView(
|
|
|
33
33
|
private var ephemeralKey: String? = null
|
|
34
34
|
private var sourceMap: ReadableMap? = null
|
|
35
35
|
private var token: ReadableMap? = null
|
|
36
|
+
private var isBounceProvisioned: Boolean = false
|
|
36
37
|
|
|
37
38
|
private var loadedSource: String? = null
|
|
38
39
|
private var currentDataSource: DataSource<CloseableReference<CloseableImage>>? = null
|
|
@@ -54,6 +55,7 @@ class AddToWalletButtonView(
|
|
|
54
55
|
cardDescription,
|
|
55
56
|
ephemeralKey,
|
|
56
57
|
token,
|
|
58
|
+
isBounceProvisioned,
|
|
57
59
|
)
|
|
58
60
|
}
|
|
59
61
|
?: run {
|
|
@@ -189,6 +191,10 @@ class AddToWalletButtonView(
|
|
|
189
191
|
token = map
|
|
190
192
|
}
|
|
191
193
|
|
|
194
|
+
fun setIsBounceProvisioned(value: Boolean) {
|
|
195
|
+
isBounceProvisioned = value
|
|
196
|
+
}
|
|
197
|
+
|
|
192
198
|
fun dispatchEvent(error: WritableMap?) {
|
|
193
199
|
UIManagerHelper
|
|
194
200
|
.getEventDispatcherForReactTag(context, id)
|
package/android/src/main/java/com/reactnativestripesdk/pushprovisioning/PushProvisioningProxy.kt
CHANGED
|
@@ -42,6 +42,7 @@ object PushProvisioningProxy {
|
|
|
42
42
|
cardDescription: String,
|
|
43
43
|
ephemeralKey: String,
|
|
44
44
|
token: ReadableMap?,
|
|
45
|
+
isBounceProvisioned: Boolean,
|
|
45
46
|
) {
|
|
46
47
|
try {
|
|
47
48
|
Class.forName("com.stripe.android.pushProvisioning.PushProvisioningActivityStarter")
|
|
@@ -50,7 +51,7 @@ object PushProvisioningProxy {
|
|
|
50
51
|
createActivityEventListener(context, view)
|
|
51
52
|
context.currentActivity?.let {
|
|
52
53
|
DefaultPushProvisioningProxy()
|
|
53
|
-
.beginPushProvisioning(it, description, EphemeralKeyProvider(ephemeralKey))
|
|
54
|
+
.beginPushProvisioning(it, description, EphemeralKeyProvider(ephemeralKey), isBounceProvisioned)
|
|
54
55
|
}
|
|
55
56
|
?: run {
|
|
56
57
|
view.dispatchEvent(
|
|
@@ -136,10 +137,10 @@ class DefaultPushProvisioningProxy {
|
|
|
136
137
|
activity: Activity,
|
|
137
138
|
description: String,
|
|
138
139
|
provider: EphemeralKeyProvider,
|
|
140
|
+
isBounceProvisioned: Boolean,
|
|
139
141
|
) {
|
|
140
|
-
PushProvisioningActivityStarter(
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
).startForResult()
|
|
142
|
+
val args = PushProvisioningActivityStarter.Args(description, provider, true)
|
|
143
|
+
args.setIsBounceProvisioned(isBounceProvisioned)
|
|
144
|
+
PushProvisioningActivityStarter(activity, args).startForResult()
|
|
144
145
|
}
|
|
145
146
|
}
|
|
@@ -12,10 +12,14 @@ import com.reactnativestripesdk.utils.getIntOr
|
|
|
12
12
|
typealias TokenCheckHandler =
|
|
13
13
|
(isCardInWallet: Boolean, token: WritableMap?, error: WritableMap?) -> Unit
|
|
14
14
|
|
|
15
|
+
typealias EligibilityCheckHandler = (canAdd: Boolean, token: WritableMap?, error: WritableMap?) -> Unit
|
|
16
|
+
|
|
15
17
|
object TapAndPayProxy {
|
|
16
18
|
private const val TAG = "StripeTapAndPay"
|
|
17
19
|
private var tapAndPayClient: Any? = null
|
|
18
20
|
const val REQUEST_CODE_TOKENIZE = 90909
|
|
21
|
+
private const val FALLBACK_MASTERCARD_CONSTANT = 3
|
|
22
|
+
private const val FALLBACK_VISA_CONSTANT = 4
|
|
19
23
|
|
|
20
24
|
private fun getTapandPayTokens(activity: Activity): Task<List<Any>>? =
|
|
21
25
|
try {
|
|
@@ -75,6 +79,93 @@ object TapAndPayProxy {
|
|
|
75
79
|
}
|
|
76
80
|
}
|
|
77
81
|
|
|
82
|
+
fun checkEligibility(
|
|
83
|
+
activity: Activity,
|
|
84
|
+
cardLastFour: String,
|
|
85
|
+
cardBrand: String,
|
|
86
|
+
callback: EligibilityCheckHandler,
|
|
87
|
+
) {
|
|
88
|
+
try {
|
|
89
|
+
val tapAndPayClass = Class.forName("com.google.android.gms.tapandpay.TapAndPay")
|
|
90
|
+
val getClientMethod = tapAndPayClass.getMethod("getClient", Activity::class.java)
|
|
91
|
+
val client = getClientMethod.invoke(null, activity)
|
|
92
|
+
|
|
93
|
+
val (network, tokenServiceProvider) = mapBrandToConstants(cardBrand)
|
|
94
|
+
val builderClass =
|
|
95
|
+
Class.forName(
|
|
96
|
+
"com.google.android.gms.tapandpay.issuer.HasEligibleTokenizationTargetRequest\$Builder"
|
|
97
|
+
)
|
|
98
|
+
val builder = builderClass.getDeclaredConstructor().newInstance()
|
|
99
|
+
builderClass.getMethod("setIdentifier", String::class.java).invoke(builder, cardLastFour)
|
|
100
|
+
builderClass.getMethod("setNetwork", Int::class.java).invoke(builder, network)
|
|
101
|
+
builderClass.getMethod("setTokenServiceProvider", Int::class.java).invoke(builder, tokenServiceProvider)
|
|
102
|
+
builderClass.getMethod("setIssuerName", String::class.java).invoke(builder, "Stripe")
|
|
103
|
+
val request = builderClass.getMethod("build").invoke(builder)
|
|
104
|
+
|
|
105
|
+
val requestClass = Class.forName("com.google.android.gms.tapandpay.issuer.HasEligibleTokenizationTargetRequest")
|
|
106
|
+
val tapAndPayClientClass = Class.forName("com.google.android.gms.tapandpay.TapAndPayClient")
|
|
107
|
+
val method = tapAndPayClientClass.getMethod("hasEligibleTokenizationTarget", requestClass)
|
|
108
|
+
|
|
109
|
+
@Suppress("UNCHECKED_CAST")
|
|
110
|
+
val task = method.invoke(client, request) as Task<Boolean>
|
|
111
|
+
task.addOnCompleteListener { completedTask ->
|
|
112
|
+
if (completedTask.isSuccessful) {
|
|
113
|
+
val hasTarget = completedTask.result
|
|
114
|
+
findExistingToken(activity, cardLastFour) { _, token, _ ->
|
|
115
|
+
callback(hasTarget, token, null)
|
|
116
|
+
}
|
|
117
|
+
} else {
|
|
118
|
+
Log.w(
|
|
119
|
+
TAG,
|
|
120
|
+
"hasEligibleTokenizationTarget failed, falling back to listTokens: " + completedTask.exception,
|
|
121
|
+
)
|
|
122
|
+
fallbackToListTokens(activity, cardLastFour, callback)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
} catch (e: Exception) {
|
|
126
|
+
Log.e(TAG, "There was a problem calling hasEligibleTokenizationTarget with Google TapAndPay: " + e.message)
|
|
127
|
+
fallbackToListTokens(activity, cardLastFour, callback)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
private fun fallbackToListTokens(
|
|
132
|
+
activity: Activity,
|
|
133
|
+
cardLastFour: String,
|
|
134
|
+
callback: EligibilityCheckHandler,
|
|
135
|
+
) {
|
|
136
|
+
findExistingToken(activity, cardLastFour) { isInWallet, token, error ->
|
|
137
|
+
if (error != null) {
|
|
138
|
+
callback(false, null, error)
|
|
139
|
+
} else {
|
|
140
|
+
callback(!isInWallet, token, null)
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
private fun mapBrandToConstants(cardBrand: String): Pair<Int, Int> {
|
|
146
|
+
try {
|
|
147
|
+
val tapAndPayClass = Class.forName("com.google.android.gms.tapandpay.TapAndPay")
|
|
148
|
+
return if (cardBrand.lowercase() == "mastercard") {
|
|
149
|
+
Pair(
|
|
150
|
+
tapAndPayClass.getField("CARD_NETWORK_MASTERCARD").getInt(null),
|
|
151
|
+
tapAndPayClass.getField("TOKEN_PROVIDER_MASTERCARD").getInt(null),
|
|
152
|
+
)
|
|
153
|
+
} else {
|
|
154
|
+
Pair(
|
|
155
|
+
tapAndPayClass.getField("CARD_NETWORK_VISA").getInt(null),
|
|
156
|
+
tapAndPayClass.getField("TOKEN_PROVIDER_VISA").getInt(null),
|
|
157
|
+
)
|
|
158
|
+
}
|
|
159
|
+
} catch (e: Exception) {
|
|
160
|
+
Log.e(TAG, "There was a problem getting TapAndPay constants: " + e.message)
|
|
161
|
+
return if (cardBrand.lowercase() == "mastercard") {
|
|
162
|
+
Pair(FALLBACK_MASTERCARD_CONSTANT, FALLBACK_MASTERCARD_CONSTANT)
|
|
163
|
+
} else {
|
|
164
|
+
Pair(FALLBACK_VISA_CONSTANT, FALLBACK_VISA_CONSTANT)
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
78
169
|
fun tokenize(
|
|
79
170
|
activity: Activity,
|
|
80
171
|
tokenReferenceId: String,
|
|
@@ -33,6 +33,9 @@ public class AddToWalletButtonManagerDelegate<T extends View, U extends BaseView
|
|
|
33
33
|
case "testEnv":
|
|
34
34
|
mViewManager.setTestEnv(view, value == null ? false : (boolean) value);
|
|
35
35
|
break;
|
|
36
|
+
case "isBounceProvisioned":
|
|
37
|
+
mViewManager.setIsBounceProvisioned(view, value == null ? false : (boolean) value);
|
|
38
|
+
break;
|
|
36
39
|
case "cardDetails":
|
|
37
40
|
mViewManager.setCardDetails(view, new DynamicFromObject(value));
|
|
38
41
|
break;
|