@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.
Files changed (145) hide show
  1. package/android/build.gradle +2 -0
  2. package/android/src/main/AndroidManifest.xml +27 -1
  3. package/android/src/main/java/com/reactnativestripesdk/EmbeddedPaymentElementViewManager.kt +2 -0
  4. package/android/src/main/java/com/reactnativestripesdk/EventEmitterCompat.kt +8 -0
  5. package/android/src/main/java/com/reactnativestripesdk/PaymentElementConfig.kt +8 -0
  6. package/android/src/main/java/com/reactnativestripesdk/PaymentMethodMessagingElementConfig.kt +147 -0
  7. package/android/src/main/java/com/reactnativestripesdk/PaymentMethodMessagingElementView.kt +164 -0
  8. package/android/src/main/java/com/reactnativestripesdk/PaymentMethodMessagingElementViewManager.kt +65 -0
  9. package/android/src/main/java/com/reactnativestripesdk/PaymentSheetAppearance.kt +1 -1
  10. package/android/src/main/java/com/reactnativestripesdk/PaymentSheetManager.kt +55 -26
  11. package/android/src/main/java/com/reactnativestripesdk/StripeConnectDeepLinkInterceptorActivity.kt +77 -0
  12. package/android/src/main/java/com/reactnativestripesdk/StripeSdkModule.kt +325 -22
  13. package/android/src/main/java/com/reactnativestripesdk/StripeSdkPackage.kt +1 -0
  14. package/android/src/main/java/com/reactnativestripesdk/customersheet/CustomerSheetManager.kt +3 -0
  15. package/android/src/main/java/com/reactnativestripesdk/utils/Errors.kt +8 -0
  16. package/android/src/main/res/xml/file_paths.xml +4 -0
  17. package/android/src/oldarch/java/com/facebook/react/viewmanagers/PaymentMethodMessagingElementViewManagerDelegate.java +36 -0
  18. package/android/src/oldarch/java/com/facebook/react/viewmanagers/PaymentMethodMessagingElementViewManagerInterface.java +18 -0
  19. package/android/src/oldarch/java/com/reactnativestripesdk/NativeStripeSdkModuleSpec.java +20 -0
  20. package/android/src/test/java/com/reactnativestripesdk/PaymentElementConfigTest.kt +37 -0
  21. package/android/src/test/java/com/reactnativestripesdk/PaymentMethodMessagingElementConfigTest.kt +543 -0
  22. package/android/src/test/java/com/reactnativestripesdk/PaymentSheetManagerTest.kt +70 -0
  23. package/ios/CustomerSheet/CustomerSheetUtils.swift +4 -0
  24. package/ios/OldArch/StripeSdkEventEmitterCompat.h +2 -0
  25. package/ios/OldArch/StripeSdkEventEmitterCompat.m +13 -1
  26. package/ios/PaymentMethodMessagingElementConfig.swift +116 -0
  27. package/ios/PaymentMethodMessagingElementHandler.m +9 -0
  28. package/ios/PaymentMethodMessagingElementView.swift +139 -0
  29. package/ios/StripeSdk.mm +40 -0
  30. package/ios/StripeSdkEmitter.swift +2 -0
  31. package/ios/StripeSdkImpl+CustomerSheet.swift +1 -0
  32. package/ios/StripeSdkImpl+Embedded.swift +4 -0
  33. package/ios/StripeSdkImpl+PaymentSheet.swift +16 -0
  34. package/ios/StripeSdkImpl.swift +132 -0
  35. package/jest/mock.js +20 -0
  36. package/lib/commonjs/connect/Components.js.map +1 -1
  37. package/lib/commonjs/connect/ConnectComponentsProvider.js +1 -1
  38. package/lib/commonjs/connect/ConnectComponentsProvider.js.map +1 -1
  39. package/lib/commonjs/connect/EmbeddedComponent.js +5 -5
  40. package/lib/commonjs/connect/EmbeddedComponent.js.map +1 -1
  41. package/lib/commonjs/connect/analytics/AnalyticsClient.js +2 -0
  42. package/lib/commonjs/connect/analytics/AnalyticsClient.js.map +1 -0
  43. package/lib/commonjs/connect/analytics/ComponentAnalyticsClient.js +2 -0
  44. package/lib/commonjs/connect/analytics/ComponentAnalyticsClient.js.map +1 -0
  45. package/lib/commonjs/connect/analytics/events.js +2 -0
  46. package/lib/commonjs/connect/analytics/events.js.map +1 -0
  47. package/lib/commonjs/connect/testUtils.js +2 -0
  48. package/lib/commonjs/connect/testUtils.js.map +1 -0
  49. package/lib/commonjs/events.js.map +1 -1
  50. package/lib/commonjs/functions.js +1 -1
  51. package/lib/commonjs/functions.js.map +1 -1
  52. package/lib/commonjs/hooks/useStripe.js +1 -1
  53. package/lib/commonjs/hooks/useStripe.js.map +1 -1
  54. package/lib/commonjs/specs/NativePaymentMethodMessagingElement.js +2 -0
  55. package/lib/commonjs/specs/NativePaymentMethodMessagingElement.js.map +1 -0
  56. package/lib/commonjs/specs/NativeStripeSdkModule.js.map +1 -1
  57. package/lib/commonjs/types/EmbeddedPaymentElement.js.map +1 -1
  58. package/lib/commonjs/types/Errors.js +1 -1
  59. package/lib/commonjs/types/Errors.js.map +1 -1
  60. package/lib/commonjs/types/PaymentSheet.js.map +1 -1
  61. package/lib/commonjs/types/components/PaymentMethodMessagingElementComponent.js +2 -0
  62. package/lib/commonjs/types/components/PaymentMethodMessagingElementComponent.js.map +1 -0
  63. package/lib/commonjs/types/index.js.map +1 -1
  64. package/lib/module/connect/Components.js.map +1 -1
  65. package/lib/module/connect/ConnectComponentsProvider.js +1 -1
  66. package/lib/module/connect/ConnectComponentsProvider.js.map +1 -1
  67. package/lib/module/connect/EmbeddedComponent.js +5 -5
  68. package/lib/module/connect/EmbeddedComponent.js.map +1 -1
  69. package/lib/module/connect/analytics/AnalyticsClient.js +2 -0
  70. package/lib/module/connect/analytics/AnalyticsClient.js.map +1 -0
  71. package/lib/module/connect/analytics/ComponentAnalyticsClient.js +2 -0
  72. package/lib/module/connect/analytics/ComponentAnalyticsClient.js.map +1 -0
  73. package/lib/module/connect/analytics/events.js +2 -0
  74. package/lib/module/connect/analytics/events.js.map +1 -0
  75. package/lib/module/connect/testUtils.js +2 -0
  76. package/lib/module/connect/testUtils.js.map +1 -0
  77. package/lib/module/events.js.map +1 -1
  78. package/lib/module/functions.js +1 -1
  79. package/lib/module/functions.js.map +1 -1
  80. package/lib/module/hooks/useStripe.js +1 -1
  81. package/lib/module/hooks/useStripe.js.map +1 -1
  82. package/lib/module/specs/NativePaymentMethodMessagingElement.js +2 -0
  83. package/lib/module/specs/NativePaymentMethodMessagingElement.js.map +1 -0
  84. package/lib/module/specs/NativeStripeSdkModule.js.map +1 -1
  85. package/lib/module/types/EmbeddedPaymentElement.js.map +1 -1
  86. package/lib/module/types/Errors.js +1 -1
  87. package/lib/module/types/Errors.js.map +1 -1
  88. package/lib/module/types/PaymentSheet.js.map +1 -1
  89. package/lib/module/types/components/PaymentMethodMessagingElementComponent.js +2 -0
  90. package/lib/module/types/components/PaymentMethodMessagingElementComponent.js.map +1 -0
  91. package/lib/module/types/index.js.map +1 -1
  92. package/lib/typescript/src/connect/Components.d.ts +91 -0
  93. package/lib/typescript/src/connect/Components.d.ts.map +1 -1
  94. package/lib/typescript/src/connect/ConnectComponentsProvider.d.ts +61 -0
  95. package/lib/typescript/src/connect/ConnectComponentsProvider.d.ts.map +1 -1
  96. package/lib/typescript/src/connect/EmbeddedComponent.d.ts.map +1 -1
  97. package/lib/typescript/src/connect/analytics/AnalyticsClient.d.ts +32 -0
  98. package/lib/typescript/src/connect/analytics/AnalyticsClient.d.ts.map +1 -0
  99. package/lib/typescript/src/connect/analytics/ComponentAnalyticsClient.d.ts +94 -0
  100. package/lib/typescript/src/connect/analytics/ComponentAnalyticsClient.d.ts.map +1 -0
  101. package/lib/typescript/src/connect/analytics/events.d.ts +215 -0
  102. package/lib/typescript/src/connect/analytics/events.d.ts.map +1 -0
  103. package/lib/typescript/src/connect/testUtils.d.ts +45 -0
  104. package/lib/typescript/src/connect/testUtils.d.ts.map +1 -0
  105. package/lib/typescript/src/events.d.ts +2 -0
  106. package/lib/typescript/src/events.d.ts.map +1 -1
  107. package/lib/typescript/src/functions.d.ts +13 -1
  108. package/lib/typescript/src/functions.d.ts.map +1 -1
  109. package/lib/typescript/src/hooks/useStripe.d.ts +2 -1
  110. package/lib/typescript/src/hooks/useStripe.d.ts.map +1 -1
  111. package/lib/typescript/src/specs/NativePaymentMethodMessagingElement.d.ts +16 -0
  112. package/lib/typescript/src/specs/NativePaymentMethodMessagingElement.d.ts.map +1 -0
  113. package/lib/typescript/src/specs/NativeStripeSdkModule.d.ts +16 -1
  114. package/lib/typescript/src/specs/NativeStripeSdkModule.d.ts.map +1 -1
  115. package/lib/typescript/src/types/CustomerSheet.d.ts +5 -0
  116. package/lib/typescript/src/types/CustomerSheet.d.ts.map +1 -1
  117. package/lib/typescript/src/types/EmbeddedPaymentElement.d.ts +5 -0
  118. package/lib/typescript/src/types/EmbeddedPaymentElement.d.ts.map +1 -1
  119. package/lib/typescript/src/types/Errors.d.ts +4 -0
  120. package/lib/typescript/src/types/Errors.d.ts.map +1 -1
  121. package/lib/typescript/src/types/PaymentSheet.d.ts +5 -0
  122. package/lib/typescript/src/types/PaymentSheet.d.ts.map +1 -1
  123. package/lib/typescript/src/types/components/PaymentMethodMessagingElementComponent.d.ts +69 -0
  124. package/lib/typescript/src/types/components/PaymentMethodMessagingElementComponent.d.ts.map +1 -0
  125. package/lib/typescript/src/types/index.d.ts +8 -1
  126. package/lib/typescript/src/types/index.d.ts.map +1 -1
  127. package/package.json +1 -1
  128. package/src/connect/Components.tsx +91 -0
  129. package/src/connect/ConnectComponentsProvider.tsx +69 -2
  130. package/src/connect/EmbeddedComponent.tsx +254 -30
  131. package/src/connect/analytics/AnalyticsClient.ts +75 -0
  132. package/src/connect/analytics/ComponentAnalyticsClient.ts +315 -0
  133. package/src/connect/analytics/events.ts +253 -0
  134. package/src/connect/testUtils.ts +37 -0
  135. package/src/events.ts +2 -0
  136. package/src/functions.ts +10 -0
  137. package/src/hooks/useStripe.tsx +8 -0
  138. package/src/specs/NativePaymentMethodMessagingElement.ts +25 -0
  139. package/src/specs/NativeStripeSdkModule.ts +21 -1
  140. package/src/types/CustomerSheet.ts +5 -0
  141. package/src/types/EmbeddedPaymentElement.tsx +5 -0
  142. package/src/types/Errors.ts +5 -0
  143. package/src/types/PaymentSheet.ts +6 -1
  144. package/src/types/components/PaymentMethodMessagingElementComponent.tsx +74 -0
  145. package/src/types/index.ts +11 -0
@@ -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
+ }
@@ -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
+ }
@@ -537,7 +537,7 @@ private fun dynamicColorFromParams(
537
537
  }
538
538
 
539
539
  @Throws(PaymentSheetAppearanceException::class)
540
- private fun getFontResId(
540
+ internal fun getFontResId(
541
541
  map: ReadableMap?,
542
542
  key: String,
543
543
  context: Context,
@@ -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
- flowController?.getPaymentOption()?.let { paymentOption ->
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
+ }