@stripe/stripe-react-native 0.67.0 → 0.69.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 (230) hide show
  1. package/android/build.gradle +3 -3
  2. package/android/gradle.properties +1 -1
  3. package/android/proguard-rules.txt +4 -0
  4. package/android/src/main/java/com/reactnativestripesdk/CurrencySelectorAppearance.kt +229 -0
  5. package/android/src/main/java/com/reactnativestripesdk/CurrencySelectorElementView.kt +8 -1
  6. package/android/src/main/java/com/reactnativestripesdk/CurrencySelectorElementViewManager.kt +11 -0
  7. package/android/src/main/java/com/reactnativestripesdk/EmbeddedPaymentElementView.kt +0 -22
  8. package/android/src/main/java/com/reactnativestripesdk/EmbeddedPaymentElementViewManager.kt +0 -22
  9. package/android/src/main/java/com/reactnativestripesdk/LinkControllerManager.kt +175 -0
  10. package/android/src/main/java/com/reactnativestripesdk/PaymentSheetManager.kt +11 -4
  11. package/android/src/main/java/com/reactnativestripesdk/StripeSdkModule.kt +43 -38
  12. package/android/src/main/java/com/reactnativestripesdk/pushprovisioning/AddToWalletButtonManager.kt +8 -0
  13. package/android/src/main/java/com/reactnativestripesdk/pushprovisioning/AddToWalletButtonView.kt +6 -0
  14. package/android/src/main/java/com/reactnativestripesdk/pushprovisioning/PushProvisioningProxy.kt +6 -5
  15. package/android/src/main/java/com/reactnativestripesdk/pushprovisioning/TapAndPayProxy.kt +91 -0
  16. package/android/src/oldarch/java/com/facebook/react/viewmanagers/AddToWalletButtonManagerDelegate.java +3 -0
  17. package/android/src/oldarch/java/com/facebook/react/viewmanagers/AddToWalletButtonManagerInterface.java +1 -0
  18. package/android/src/oldarch/java/com/facebook/react/viewmanagers/EmbeddedPaymentElementViewManagerDelegate.java +0 -3
  19. package/android/src/oldarch/java/com/facebook/react/viewmanagers/EmbeddedPaymentElementViewManagerInterface.java +0 -1
  20. package/android/src/oldarch/java/com/facebook/react/viewmanagers/StripeCurrencySelectorElementManagerDelegate.java +4 -0
  21. package/android/src/oldarch/java/com/facebook/react/viewmanagers/StripeCurrencySelectorElementManagerInterface.java +2 -0
  22. package/android/src/oldarch/java/com/reactnativestripesdk/NativeStripeSdkModuleSpec.java +8 -8
  23. package/android/src/onramp/java/com/reactnativestripesdk/OnrampErrors.kt +9 -28
  24. package/android/src/onramp/java/com/reactnativestripesdk/OnrampSdkModule.kt +10 -9
  25. package/android/src/test/java/com/reactnativestripesdk/CurrencySelectorAppearanceTest.kt +137 -0
  26. package/android/src/test/java/com/reactnativestripesdk/OnrampErrorsTest.kt +20 -18
  27. package/ios/CurrencySelectorAppearance.swift +120 -0
  28. package/ios/CurrencySelectorElementManager.m +1 -0
  29. package/ios/CurrencySelectorElementView.swift +17 -2
  30. package/ios/Mappers+Checkout.swift +3 -3
  31. package/ios/NewArch/EmbeddedPaymentElementViewComponentView.mm +0 -4
  32. package/ios/NewArch/StripeCurrencySelectorElementComponentView.mm +1 -0
  33. package/ios/OnrampErrors.swift +1 -14
  34. package/ios/StripeSdk.mm +15 -22
  35. package/ios/StripeSdkImpl+Checkout.swift +15 -39
  36. package/ios/StripeSdkImpl+Embedded.swift +0 -24
  37. package/ios/StripeSdkImpl+LinkController.swift +108 -0
  38. package/ios/StripeSdkImpl+PaymentSheet.swift +1 -2
  39. package/ios/StripeSdkImpl.swift +8 -3
  40. package/lib/commonjs/components/AddToWalletButton.js +1 -1
  41. package/lib/commonjs/components/AddToWalletButton.js.map +1 -1
  42. package/lib/commonjs/components/AddressSheet.js +1 -1
  43. package/lib/commonjs/components/AddressSheet.js.map +1 -1
  44. package/lib/commonjs/components/AuBECSDebitForm.js +1 -1
  45. package/lib/commonjs/components/AuBECSDebitForm.js.map +1 -1
  46. package/lib/commonjs/components/CardField.js +1 -1
  47. package/lib/commonjs/components/CardField.js.map +1 -1
  48. package/lib/commonjs/components/CardForm.js +1 -1
  49. package/lib/commonjs/components/CardForm.js.map +1 -1
  50. package/lib/commonjs/components/CurrencySelectorElement.js +1 -1
  51. package/lib/commonjs/components/CurrencySelectorElement.js.map +1 -1
  52. package/lib/commonjs/components/PaymentMethodMessagingElement.js +1 -1
  53. package/lib/commonjs/components/PaymentMethodMessagingElement.js.map +1 -1
  54. package/lib/commonjs/components/PlatformPayButton.js +1 -1
  55. package/lib/commonjs/components/PlatformPayButton.js.map +1 -1
  56. package/lib/commonjs/components/StripeContainer.js +1 -1
  57. package/lib/commonjs/components/StripeContainer.js.map +1 -1
  58. package/lib/commonjs/connect/Components.js +1 -1
  59. package/lib/commonjs/connect/Components.js.map +1 -1
  60. package/lib/commonjs/connect/ConnectComponentsProvider.js +1 -1
  61. package/lib/commonjs/connect/ConnectComponentsProvider.js.map +1 -1
  62. package/lib/commonjs/connect/EmbeddedComponent.js +2 -2
  63. package/lib/commonjs/connect/EmbeddedComponent.js.map +1 -1
  64. package/lib/commonjs/connect/ModalCloseButton.js +1 -1
  65. package/lib/commonjs/connect/ModalCloseButton.js.map +1 -1
  66. package/lib/commonjs/connect/NavigationBar.js +1 -1
  67. package/lib/commonjs/connect/NavigationBar.js.map +1 -1
  68. package/lib/commonjs/functions.js +1 -1
  69. package/lib/commonjs/functions.js.map +1 -1
  70. package/lib/commonjs/helpers.js +1 -1
  71. package/lib/commonjs/hooks/useCheckout.js +1 -1
  72. package/lib/commonjs/hooks/useCheckout.js.map +1 -1
  73. package/lib/commonjs/hooks/useLinkController.js +2 -0
  74. package/lib/commonjs/hooks/useLinkController.js.map +1 -0
  75. package/lib/commonjs/index.js +1 -1
  76. package/lib/commonjs/index.js.map +1 -1
  77. package/lib/commonjs/specs/NativeAddToWalletButton.js +1 -1
  78. package/lib/commonjs/specs/NativeAddToWalletButton.js.map +1 -1
  79. package/lib/commonjs/specs/NativeAddressSheet.js +1 -1
  80. package/lib/commonjs/specs/NativeApplePayButton.js +1 -1
  81. package/lib/commonjs/specs/NativeAuBECSDebitForm.js +1 -1
  82. package/lib/commonjs/specs/NativeCardField.js +1 -1
  83. package/lib/commonjs/specs/NativeCardField.js.map +1 -1
  84. package/lib/commonjs/specs/NativeCardForm.js +1 -1
  85. package/lib/commonjs/specs/NativeCardForm.js.map +1 -1
  86. package/lib/commonjs/specs/NativeConnectAccountOnboardingView.js +1 -1
  87. package/lib/commonjs/specs/NativeCurrencySelectorElement.js +1 -1
  88. package/lib/commonjs/specs/NativeCurrencySelectorElement.js.map +1 -1
  89. package/lib/commonjs/specs/NativeEmbeddedPaymentElement.js +1 -1
  90. package/lib/commonjs/specs/NativeEmbeddedPaymentElement.js.map +1 -1
  91. package/lib/commonjs/specs/NativeGooglePayButton.js +1 -1
  92. package/lib/commonjs/specs/NativeNavigationBar.js +1 -1
  93. package/lib/commonjs/specs/NativePaymentMethodMessagingElement.js +1 -1
  94. package/lib/commonjs/specs/NativeStripeContainer.js +1 -1
  95. package/lib/commonjs/specs/NativeStripeSdkModule.js.map +1 -1
  96. package/lib/commonjs/types/EmbeddedPaymentElement.js +1 -1
  97. package/lib/commonjs/types/EmbeddedPaymentElement.js.map +1 -1
  98. package/lib/commonjs/types/Errors.js +1 -1
  99. package/lib/commonjs/types/Errors.js.map +1 -1
  100. package/lib/commonjs/types/LinkController.js +2 -0
  101. package/lib/commonjs/types/LinkController.js.map +1 -0
  102. package/lib/commonjs/types/PushProvisioning.js.map +1 -1
  103. package/lib/commonjs/types/index.js +1 -1
  104. package/lib/commonjs/types/index.js.map +1 -1
  105. package/lib/module/components/AddToWalletButton.js +1 -1
  106. package/lib/module/components/AddToWalletButton.js.map +1 -1
  107. package/lib/module/components/AddressSheet.js +1 -1
  108. package/lib/module/components/AddressSheet.js.map +1 -1
  109. package/lib/module/components/AuBECSDebitForm.js +1 -1
  110. package/lib/module/components/AuBECSDebitForm.js.map +1 -1
  111. package/lib/module/components/CardField.js +1 -1
  112. package/lib/module/components/CardField.js.map +1 -1
  113. package/lib/module/components/CardForm.js +1 -1
  114. package/lib/module/components/CardForm.js.map +1 -1
  115. package/lib/module/components/CurrencySelectorElement.js +1 -1
  116. package/lib/module/components/CurrencySelectorElement.js.map +1 -1
  117. package/lib/module/components/PaymentMethodMessagingElement.js +1 -1
  118. package/lib/module/components/PaymentMethodMessagingElement.js.map +1 -1
  119. package/lib/module/components/PlatformPayButton.js +1 -1
  120. package/lib/module/components/PlatformPayButton.js.map +1 -1
  121. package/lib/module/components/StripeContainer.js +1 -1
  122. package/lib/module/components/StripeContainer.js.map +1 -1
  123. package/lib/module/connect/Components.js +1 -1
  124. package/lib/module/connect/Components.js.map +1 -1
  125. package/lib/module/connect/ConnectComponentsProvider.js +1 -1
  126. package/lib/module/connect/ConnectComponentsProvider.js.map +1 -1
  127. package/lib/module/connect/EmbeddedComponent.js +2 -2
  128. package/lib/module/connect/EmbeddedComponent.js.map +1 -1
  129. package/lib/module/connect/ModalCloseButton.js +1 -1
  130. package/lib/module/connect/ModalCloseButton.js.map +1 -1
  131. package/lib/module/connect/NavigationBar.js +1 -1
  132. package/lib/module/connect/NavigationBar.js.map +1 -1
  133. package/lib/module/functions.js +1 -1
  134. package/lib/module/functions.js.map +1 -1
  135. package/lib/module/helpers.js +1 -1
  136. package/lib/module/hooks/useCheckout.js +1 -1
  137. package/lib/module/hooks/useCheckout.js.map +1 -1
  138. package/lib/module/hooks/useLinkController.js +2 -0
  139. package/lib/module/hooks/useLinkController.js.map +1 -0
  140. package/lib/module/index.js +1 -1
  141. package/lib/module/index.js.map +1 -1
  142. package/lib/module/specs/NativeAddToWalletButton.js +1 -1
  143. package/lib/module/specs/NativeAddToWalletButton.js.map +1 -1
  144. package/lib/module/specs/NativeAddressSheet.js +1 -1
  145. package/lib/module/specs/NativeApplePayButton.js +1 -1
  146. package/lib/module/specs/NativeAuBECSDebitForm.js +1 -1
  147. package/lib/module/specs/NativeCardField.js +1 -1
  148. package/lib/module/specs/NativeCardField.js.map +1 -1
  149. package/lib/module/specs/NativeCardForm.js +1 -1
  150. package/lib/module/specs/NativeCardForm.js.map +1 -1
  151. package/lib/module/specs/NativeConnectAccountOnboardingView.js +1 -1
  152. package/lib/module/specs/NativeCurrencySelectorElement.js +1 -1
  153. package/lib/module/specs/NativeCurrencySelectorElement.js.map +1 -1
  154. package/lib/module/specs/NativeEmbeddedPaymentElement.js +1 -1
  155. package/lib/module/specs/NativeEmbeddedPaymentElement.js.map +1 -1
  156. package/lib/module/specs/NativeGooglePayButton.js +1 -1
  157. package/lib/module/specs/NativeNavigationBar.js +1 -1
  158. package/lib/module/specs/NativePaymentMethodMessagingElement.js +1 -1
  159. package/lib/module/specs/NativeStripeContainer.js +1 -1
  160. package/lib/module/specs/NativeStripeSdkModule.js.map +1 -1
  161. package/lib/module/types/EmbeddedPaymentElement.js +1 -1
  162. package/lib/module/types/EmbeddedPaymentElement.js.map +1 -1
  163. package/lib/module/types/Errors.js +1 -1
  164. package/lib/module/types/Errors.js.map +1 -1
  165. package/lib/module/types/LinkController.js +2 -0
  166. package/lib/module/types/LinkController.js.map +1 -0
  167. package/lib/module/types/PushProvisioning.js.map +1 -1
  168. package/lib/module/types/index.js +1 -1
  169. package/lib/module/types/index.js.map +1 -1
  170. package/lib/typescript/src/components/AddToWalletButton.d.ts +2 -0
  171. package/lib/typescript/src/components/AddToWalletButton.d.ts.map +1 -1
  172. package/lib/typescript/src/components/CurrencySelectorElement.d.ts +76 -1
  173. package/lib/typescript/src/components/CurrencySelectorElement.d.ts.map +1 -1
  174. package/lib/typescript/src/connect/Components.d.ts +1 -1
  175. package/lib/typescript/src/connect/Components.d.ts.map +1 -1
  176. package/lib/typescript/src/connect/EmbeddedComponent.d.ts +23 -0
  177. package/lib/typescript/src/connect/EmbeddedComponent.d.ts.map +1 -1
  178. package/lib/typescript/src/functions.d.ts +19 -1
  179. package/lib/typescript/src/functions.d.ts.map +1 -1
  180. package/lib/typescript/src/hooks/useCheckout.d.ts.map +1 -1
  181. package/lib/typescript/src/hooks/useLinkController.d.ts +28 -0
  182. package/lib/typescript/src/hooks/useLinkController.d.ts.map +1 -0
  183. package/lib/typescript/src/index.d.ts +1 -0
  184. package/lib/typescript/src/index.d.ts.map +1 -1
  185. package/lib/typescript/src/specs/NativeAddToWalletButton.d.ts +1 -0
  186. package/lib/typescript/src/specs/NativeAddToWalletButton.d.ts.map +1 -1
  187. package/lib/typescript/src/specs/NativeCurrencySelectorElement.d.ts +4 -0
  188. package/lib/typescript/src/specs/NativeCurrencySelectorElement.d.ts.map +1 -1
  189. package/lib/typescript/src/specs/NativeEmbeddedPaymentElement.d.ts +0 -1
  190. package/lib/typescript/src/specs/NativeEmbeddedPaymentElement.d.ts.map +1 -1
  191. package/lib/typescript/src/specs/NativeStripeSdkModule.d.ts +5 -3
  192. package/lib/typescript/src/specs/NativeStripeSdkModule.d.ts.map +1 -1
  193. package/lib/typescript/src/types/Checkout.d.ts +1 -14
  194. package/lib/typescript/src/types/Checkout.d.ts.map +1 -1
  195. package/lib/typescript/src/types/EmbeddedPaymentElement.d.ts +1 -10
  196. package/lib/typescript/src/types/EmbeddedPaymentElement.d.ts.map +1 -1
  197. package/lib/typescript/src/types/Errors.d.ts +8 -0
  198. package/lib/typescript/src/types/Errors.d.ts.map +1 -1
  199. package/lib/typescript/src/types/LinkController.d.ts +71 -0
  200. package/lib/typescript/src/types/LinkController.d.ts.map +1 -0
  201. package/lib/typescript/src/types/Onramp.d.ts +0 -13
  202. package/lib/typescript/src/types/Onramp.d.ts.map +1 -1
  203. package/lib/typescript/src/types/PaymentMethod.d.ts +2 -0
  204. package/lib/typescript/src/types/PaymentMethod.d.ts.map +1 -1
  205. package/lib/typescript/src/types/PushProvisioning.d.ts +2 -0
  206. package/lib/typescript/src/types/PushProvisioning.d.ts.map +1 -1
  207. package/lib/typescript/src/types/index.d.ts +2 -1
  208. package/lib/typescript/src/types/index.d.ts.map +1 -1
  209. package/package.json +1 -1
  210. package/src/components/AddToWalletButton.tsx +2 -0
  211. package/src/components/CurrencySelectorElement.tsx +101 -1
  212. package/src/connect/Components.tsx +10 -0
  213. package/src/connect/EmbeddedComponent.tsx +47 -10
  214. package/src/functions.ts +48 -0
  215. package/src/hooks/useCheckout.tsx +0 -14
  216. package/src/hooks/useLinkController.tsx +54 -0
  217. package/src/index.tsx +1 -0
  218. package/src/specs/NativeAddToWalletButton.ts +1 -0
  219. package/src/specs/NativeCurrencySelectorElement.ts +5 -0
  220. package/src/specs/NativeEmbeddedPaymentElement.ts +1 -10
  221. package/src/specs/NativeStripeSdkModule.ts +11 -10
  222. package/src/types/Checkout.ts +1 -19
  223. package/src/types/EmbeddedPaymentElement.tsx +4 -27
  224. package/src/types/Errors.ts +9 -0
  225. package/src/types/LinkController.ts +75 -0
  226. package/src/types/Onramp.ts +0 -14
  227. package/src/types/PaymentMethod.ts +2 -0
  228. package/src/types/PushProvisioning.ts +2 -0
  229. package/src/types/index.ts +2 -0
  230. package/stripe-react-native.podspec +1 -1
@@ -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.1.0"
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.10.+
6
+ StripeSdk_stripeVersion=23.12.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
@@ -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()
@@ -0,0 +1,175 @@
1
+ package com.reactnativestripesdk
2
+
3
+ import androidx.activity.ComponentActivity
4
+ import androidx.lifecycle.SavedStateHandle
5
+ import androidx.lifecycle.lifecycleScope
6
+ import com.facebook.react.bridge.Arguments
7
+ import com.facebook.react.bridge.Promise
8
+ import com.facebook.react.bridge.ReactApplicationContext
9
+ import com.facebook.react.bridge.ReadableMap
10
+ import com.reactnativestripesdk.utils.ErrorType
11
+ import com.reactnativestripesdk.utils.createError
12
+ import com.reactnativestripesdk.utils.mapFromPaymentMethod
13
+ import com.stripe.android.link.LinkController
14
+ import com.stripe.android.link.LinkControllerPreview
15
+ import kotlinx.coroutines.Dispatchers
16
+ import kotlinx.coroutines.launch
17
+ import kotlinx.coroutines.withContext
18
+
19
+ internal const val LINK_CONTROLLER_NOT_INITIALIZED_ERROR =
20
+ "LinkController has not been initialized. Call initLinkController first."
21
+
22
+ /**
23
+ * Manages [LinkController] initialization and presentation for the React Native bridge.
24
+ *
25
+ * @PrivatePreview
26
+ */
27
+ @OptIn(LinkControllerPreview::class)
28
+ internal class LinkControllerManager(
29
+ private val context: ReactApplicationContext,
30
+ private val publishableKey: String,
31
+ private val stripeAccountId: String?,
32
+ ) {
33
+ private var linkController: LinkController? = null
34
+ private var linkPresenter: LinkController.Presenter? = null
35
+ private var presentPromise: Promise? = null
36
+
37
+ fun configure(params: ReadableMap, promise: Promise) {
38
+ val email = params.getString("email")
39
+ val merchantDisplayName = params.getString("merchantDisplayName")
40
+ if (merchantDisplayName == null) {
41
+ promise.resolve(createError(ErrorType.Failed.toString(), "merchantDisplayName is required."))
42
+ return
43
+ }
44
+
45
+ val activity = context.currentActivity as? ComponentActivity
46
+ if (activity == null) {
47
+ promise.resolve(createError(ErrorType.Failed.toString(), "Activity is not available. Retry this method."))
48
+ return
49
+ }
50
+
51
+ val phoneNumber = params.getString("phoneNumber")
52
+ val allowLogout = if (params.hasKey("allowLogout")) params.getBoolean("allowLogout") else true
53
+ val setupIntentClientSecret = params.getString("setupIntentClientSecret")
54
+
55
+ val supportedTypes = parseSupportedPaymentMethodTypes(params)
56
+
57
+ val config = LinkController.Configuration(
58
+ merchantDisplayName = merchantDisplayName,
59
+ publishableKey = publishableKey,
60
+ stripeAccountId = stripeAccountId
61
+ )
62
+ .also { if (email != null) it.email(email) }
63
+ .phoneNumber(phoneNumber)
64
+ .supportedPaymentMethodTypes(supportedTypes)
65
+ .allowLogout(allowLogout)
66
+ .setupIntentClientSecret(setupIntentClientSecret)
67
+
68
+ // Build a new controller if this is the first call or after a reset.
69
+ // SavedStateHandle() is empty — state will not survive process death, which is
70
+ // the same trade-off made by other Stripe RN SDK APIs.
71
+ if (linkController == null) {
72
+ linkController = LinkController.Builder(
73
+ application = activity.application,
74
+ savedStateHandle = SavedStateHandle()
75
+ ).build()
76
+ }
77
+
78
+ val controller = linkController!!
79
+
80
+ activity.lifecycleScope.launch {
81
+ val result = controller.configure(config)
82
+ if (result.isSuccess) {
83
+ // Create once: presenter may register ActivityResultLaunchers, which must happen before onStart().
84
+ if (linkPresenter == null) {
85
+ linkPresenter = controller.createPresenter(
86
+ activity = activity,
87
+ presentCallback = { presentResult -> handlePresentResult(presentResult) }
88
+ )
89
+ }
90
+ promise.resolve(Arguments.createMap())
91
+ } else {
92
+ promise.resolve(
93
+ createError(
94
+ ErrorType.Failed.toString(),
95
+ result.exceptionOrNull()?.message ?: "LinkController configuration failed."
96
+ )
97
+ )
98
+ }
99
+ }
100
+ }
101
+
102
+ fun present(promise: Promise) {
103
+ val presenter = linkPresenter
104
+ if (presenter == null) {
105
+ promise.resolve(createError(ErrorType.Failed.toString(), LINK_CONTROLLER_NOT_INITIALIZED_ERROR))
106
+ return
107
+ }
108
+
109
+ presentPromise = promise
110
+ presenter.present()
111
+ }
112
+
113
+ fun destroy() {
114
+ linkController = null
115
+ linkPresenter = null
116
+ presentPromise = null
117
+ }
118
+
119
+ private fun parseSupportedPaymentMethodTypes(params: ReadableMap): List<LinkController.PaymentMethodType>? =
120
+ params.getArray("supportedPaymentMethodTypes")?.let { arr ->
121
+ (0 until arr.size()).mapNotNull { i ->
122
+ when (arr.getString(i)) {
123
+ "card" -> LinkController.PaymentMethodType.Card
124
+ "bankAccount" -> LinkController.PaymentMethodType.BankAccount
125
+ else -> null
126
+ }
127
+ }.ifEmpty { null }
128
+ }
129
+
130
+ private fun handlePresentResult(result: LinkController.PresentResult) {
131
+ val promise = presentPromise ?: return
132
+ val controller = linkController ?: return
133
+ when (result) {
134
+ is LinkController.PresentResult.Completed -> {
135
+ val response = Arguments.createMap()
136
+ response.putMap("paymentMethod", mapFromPaymentMethod(result.paymentMethod))
137
+
138
+ val preview = controller.paymentMethodPreview.value
139
+ val currentScope = (context.currentActivity as? ComponentActivity)?.lifecycleScope
140
+ if (preview != null && currentScope != null) {
141
+ currentScope.launch {
142
+ val iconBase64 = withContext(Dispatchers.IO) {
143
+ try {
144
+ convertDrawableToBase64(preview.icon)
145
+ } catch (_: Exception) {
146
+ null
147
+ }
148
+ }
149
+ val previewMap = Arguments.createMap()
150
+ previewMap.putString("label", preview.label)
151
+ preview.sublabel?.let { previewMap.putString("sublabel", it) }
152
+ if (iconBase64 != null) {
153
+ previewMap.putString("icon", "data:image/png;base64,$iconBase64")
154
+ }
155
+ response.putMap("paymentMethodPreview", previewMap)
156
+ promise.resolve(response)
157
+ }
158
+ } else {
159
+ promise.resolve(response)
160
+ }
161
+ }
162
+ is LinkController.PresentResult.Canceled -> {
163
+ promise.resolve(
164
+ createError(
165
+ ErrorType.Canceled.toString(),
166
+ "The customer canceled the Link flow."
167
+ )
168
+ )
169
+ }
170
+ is LinkController.PresentResult.Failed -> {
171
+ promise.resolve(createError(ErrorType.Failed.toString(), result.error))
172
+ }
173
+ }
174
+ }
175
+ }
@@ -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
- val checkout = getCheckoutInstanceOrResolveError(promise)
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,