@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
@@ -0,0 +1,77 @@
1
+ package com.reactnativestripesdk
2
+
3
+ import android.app.Activity
4
+ import android.content.Intent
5
+ import android.os.Bundle
6
+ import android.util.Log
7
+
8
+ /**
9
+ * Transparent interceptor Activity that captures stripe-connect:// deep links
10
+ * before they reach React Native's Linking module.
11
+ *
12
+ * This prevents Expo Router from receiving the URL and dismissing the current screen.
13
+ * The URL is stored in StripeSdkModule's internal storage and retrieved via polling.
14
+ *
15
+ * HOW IT WORKS:
16
+ * 1. Android launches this Activity when a stripe-connect:// URL is opened
17
+ * 2. onCreate() extracts the URL and stores it via StripeSdkModule.storeStripeConnectDeepLink()
18
+ * 3. Launches an Intent to bring the main app to the foreground
19
+ * 4. The Activity immediately finishes without showing any UI
20
+ * 5. JavaScript polls for URLs via NativeStripeSdk.pollAndClearPendingStripeConnectUrls()
21
+ *
22
+ * MANIFEST CONFIGURATION:
23
+ * The AndroidManifest.xml declares this Activity with:
24
+ * - android:exported="true" - allows deep links from outside the app
25
+ * - android:launchMode="singleTask" - reuses existing instance if available
26
+ * - android:theme="@android:style/Theme.Translucent.NoTitleBar" - transparent UI
27
+ * - Intent filter for stripe-connect:// scheme
28
+ *
29
+ * USER IMPACT:
30
+ * - Zero configuration required by users
31
+ * - Works automatically for all package names
32
+ * - No MainActivity override needed
33
+ */
34
+ class StripeConnectDeepLinkInterceptorActivity : Activity() {
35
+ override fun onCreate(savedInstanceState: Bundle?) {
36
+ super.onCreate(savedInstanceState)
37
+
38
+ // Extract the deep link URL from the Intent
39
+ val url = intent?.data?.toString()
40
+
41
+ if (url != null && url.startsWith("stripe-connect://")) {
42
+ // Store in SDK's internal storage (thread-safe)
43
+ StripeSdkModule.storeStripeConnectDeepLink(url)
44
+ } else {
45
+ Log.w(TAG, "Unexpected URL scheme in StripeConnectDeepLinkInterceptor: $url")
46
+ }
47
+
48
+ // Bring the main app back to the foreground
49
+ // This is critical because when Custom Tabs opens a deep link,
50
+ // the interceptor Activity starts in a new task. When it finishes,
51
+ // Android doesn't automatically return to the main app - it just shows
52
+ // the Custom Tab again. We need to explicitly bring the app forward.
53
+ try {
54
+ val packageName = applicationContext.packageName
55
+ val launchIntent = packageManager.getLaunchIntentForPackage(packageName)
56
+
57
+ if (launchIntent != null) {
58
+ // Flags to bring existing task to front without recreating activities
59
+ launchIntent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)
60
+ launchIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
61
+ startActivity(launchIntent)
62
+ } else {
63
+ Log.w(TAG, "Could not get launch intent for package: $packageName")
64
+ }
65
+ } catch (e: Exception) {
66
+ Log.e(TAG, "Error bringing app to foreground", e)
67
+ }
68
+
69
+ // Immediately finish - don't show any UI
70
+ // This makes the Activity transparent to the user
71
+ finish()
72
+ }
73
+
74
+ companion object {
75
+ private const val TAG = "StripeConnectInterceptor"
76
+ }
77
+ }
@@ -5,9 +5,13 @@ import android.app.Activity
5
5
  import android.app.Application
6
6
  import android.content.Intent
7
7
  import android.os.Bundle
8
+ import android.os.Handler
9
+ import android.os.Looper
8
10
  import android.util.Log
9
11
  import android.view.ViewGroup
12
+ import androidx.appcompat.app.AppCompatActivity
10
13
  import androidx.browser.customtabs.CustomTabsIntent
14
+ import androidx.core.net.toUri
11
15
  import androidx.fragment.app.FragmentActivity
12
16
  import com.facebook.react.ReactActivity
13
17
  import com.facebook.react.bridge.Arguments
@@ -19,6 +23,7 @@ import com.facebook.react.bridge.ReadableArray
19
23
  import com.facebook.react.bridge.ReadableMap
20
24
  import com.facebook.react.bridge.UiThreadUtil
21
25
  import com.facebook.react.bridge.WritableMap
26
+ import com.facebook.react.bridge.WritableNativeMap
22
27
  import com.facebook.react.module.annotations.ReactModule
23
28
  import com.reactnativestripesdk.addresssheet.AddressLauncherManager
24
29
  import com.reactnativestripesdk.customersheet.CustomerSheetManager
@@ -63,6 +68,7 @@ import com.stripe.android.model.ConfirmPaymentIntentParams
63
68
  import com.stripe.android.model.ConfirmSetupIntentParams
64
69
  import com.stripe.android.model.PaymentIntent
65
70
  import com.stripe.android.model.PaymentMethod
71
+ import com.stripe.android.model.RadarSession
66
72
  import com.stripe.android.model.SetupIntent
67
73
  import com.stripe.android.model.Token
68
74
  import com.stripe.android.payments.bankaccount.CollectBankAccountConfiguration
@@ -106,6 +112,10 @@ class StripeSdkModule(
106
112
 
107
113
  internal var composeCompatView: StripeAbstractComposeView.CompatView? = null
108
114
 
115
+ // Storage for pending stripe-connect:// deep link URLs to prevent Expo Router from receiving them
116
+ private val pendingStripeConnectUrls = mutableListOf<String>()
117
+ private val pendingUrlsLock = Any()
118
+
109
119
  val eventEmitter: EventEmitterCompat by lazy { EventEmitterCompat(reactApplicationContext) }
110
120
 
111
121
  private val mActivityEventListener =
@@ -128,11 +138,6 @@ class StripeSdkModule(
128
138
  it,
129
139
  )
130
140
  createPlatformPayPaymentMethodPromise = null
131
- } ?: run {
132
- Log.d(
133
- "StripeReactNative",
134
- "No promise was found, Google Pay result went unhandled,",
135
- )
136
141
  }
137
142
  }
138
143
  }
@@ -179,14 +184,38 @@ class StripeSdkModule(
179
184
  }
180
185
 
181
186
  @SuppressLint("RestrictedApi")
182
- override fun getTypedExportedConstants() =
183
- mapOf(
187
+ override fun getTypedExportedConstants(): Map<String, Any> {
188
+ val packageInfo =
189
+ try {
190
+ reactApplicationContext.packageManager.getPackageInfo(
191
+ reactApplicationContext.packageName,
192
+ 0,
193
+ )
194
+ } catch (e: Exception) {
195
+ null
196
+ }
197
+
198
+ return mapOf(
184
199
  "API_VERSIONS" to
185
200
  mapOf(
186
201
  "CORE" to ApiVersion.API_VERSION_CODE,
187
202
  "ISSUING" to PushProvisioningProxy.getApiVersion(),
188
203
  ),
204
+ "SYSTEM_INFO" to
205
+ mapOf(
206
+ "sdkVersion" to STRIPE_ANDROID_SDK_VERSION,
207
+ "osVersion" to android.os.Build.VERSION.RELEASE,
208
+ "deviceType" to "${android.os.Build.MANUFACTURER} ${android.os.Build.MODEL}",
209
+ "appName" to (
210
+ reactApplicationContext.applicationInfo
211
+ .loadLabel(
212
+ reactApplicationContext.packageManager,
213
+ ).toString()
214
+ ),
215
+ "appVersion" to (packageInfo?.versionName ?: ""),
216
+ ),
189
217
  )
218
+ }
190
219
 
191
220
  @ReactMethod
192
221
  override fun initialise(
@@ -1300,6 +1329,31 @@ class StripeSdkModule(
1300
1329
  }
1301
1330
  }
1302
1331
 
1332
+ @ReactMethod
1333
+ override fun createRadarSession(promise: Promise) {
1334
+ if (!::stripe.isInitialized) {
1335
+ promise.resolve(createMissingInitError())
1336
+ return
1337
+ }
1338
+
1339
+ stripe.createRadarSession(
1340
+ stripeAccountId = stripeAccountId,
1341
+ callback =
1342
+ object : com.stripe.android.ApiResultCallback<RadarSession> {
1343
+ override fun onSuccess(session: RadarSession) {
1344
+ val result = WritableNativeMap()
1345
+ result.putString("id", session.id)
1346
+ promise.resolve(result)
1347
+ }
1348
+
1349
+ override fun onError(e: Exception) {
1350
+ promise.resolve(createError(ErrorType.Failed.toString(), e))
1351
+ }
1352
+ },
1353
+ activity = getCurrentActivityOrResolveWithError(promise) as? AppCompatActivity,
1354
+ )
1355
+ }
1356
+
1303
1357
  @ReactMethod
1304
1358
  override fun createEmbeddedPaymentElement(
1305
1359
  intentConfig: ReadableMap,
@@ -1347,11 +1401,12 @@ class StripeSdkModule(
1347
1401
  url: String,
1348
1402
  promise: Promise,
1349
1403
  ) {
1404
+ isAuthWebViewActive = true
1350
1405
  val activity = getCurrentActivityOrResolveWithError(promise) ?: return
1351
1406
 
1352
1407
  UiThreadUtil.runOnUiThread {
1353
1408
  try {
1354
- val uri = android.net.Uri.parse(url)
1409
+ val uri = url.toUri()
1355
1410
  val builder =
1356
1411
  androidx.browser.customtabs.CustomTabsIntent
1357
1412
  .Builder()
@@ -1362,18 +1417,214 @@ class StripeSdkModule(
1362
1417
 
1363
1418
  val customTabsIntent = builder.build()
1364
1419
 
1420
+ // NOTE: We intentionally do NOT use FLAG_ACTIVITY_NO_HISTORY here.
1421
+ // That flag can cause React Native's state restoration to fail when returning from Custom Tabs,
1422
+ // resulting in the navigation stack being reset and the previous screen being dismissed.
1423
+ // Custom Tabs will be properly cleaned up when the user navigates back or the session completes.
1424
+
1365
1425
  // Note: Custom Tabs doesn't have built-in redirect handling like iOS ASWebAuthenticationSession.
1366
1426
  // The redirect will be handled via deep linking when the auth server redirects to stripe-connect://
1367
1427
  // The React Native Linking module will capture the deep link and pass it back to the JS layer.
1368
1428
  customTabsIntent.launchUrl(activity, uri)
1369
1429
 
1430
+ // Fallback: Reset after timeout if JavaScript doesn't call authWebViewDeepLinkHandled
1431
+ Handler(Looper.getMainLooper()).postDelayed({
1432
+ if (isAuthWebViewActive) {
1433
+ isAuthWebViewActive = false
1434
+ }
1435
+ }, AUTH_WEBVIEW_FALLBACK_TIMEOUT_MS)
1436
+
1370
1437
  promise.resolve(null)
1371
1438
  } catch (e: Exception) {
1439
+ isAuthWebViewActive = false
1372
1440
  promise.resolve(createError("Failed", e))
1373
1441
  }
1374
1442
  }
1375
1443
  }
1376
1444
 
1445
+ @ReactMethod
1446
+ override fun downloadAndShareFile(
1447
+ url: String,
1448
+ filename: String?,
1449
+ promise: Promise,
1450
+ ) {
1451
+ CoroutineScope(Dispatchers.IO).launch {
1452
+ try {
1453
+ // Download file
1454
+ val client = okhttp3.OkHttpClient()
1455
+ val request =
1456
+ okhttp3.Request
1457
+ .Builder()
1458
+ .url(url)
1459
+ .build()
1460
+ val response = client.newCall(request).execute()
1461
+
1462
+ if (!response.isSuccessful) {
1463
+ promise.resolve(
1464
+ Arguments.createMap().apply {
1465
+ putBoolean("success", false)
1466
+ putString("error", "NetworkError")
1467
+ putString("message", "HTTP ${response.code}")
1468
+ },
1469
+ )
1470
+ return@launch
1471
+ }
1472
+
1473
+ // Save to cache directory
1474
+ val exportsDir = java.io.File(reactApplicationContext.cacheDir, "stripe-exports")
1475
+ exportsDir.mkdirs()
1476
+
1477
+ val file = java.io.File(exportsDir, "export-${java.util.UUID.randomUUID()}.csv")
1478
+
1479
+ response.body?.byteStream()?.use { input ->
1480
+ file.outputStream().use { output ->
1481
+ input.copyTo(output)
1482
+ }
1483
+ }
1484
+
1485
+ // Share on main thread
1486
+ UiThreadUtil.runOnUiThread {
1487
+ shareFile(file, promise)
1488
+ }
1489
+ } catch (e: Exception) {
1490
+ promise.resolve(
1491
+ Arguments.createMap().apply {
1492
+ putBoolean("success", false)
1493
+ putString("error", "DownloadFailed")
1494
+ putString("message", e.message ?: "Unknown error")
1495
+ },
1496
+ )
1497
+ }
1498
+ }
1499
+ }
1500
+
1501
+ private fun shareFile(
1502
+ file: java.io.File,
1503
+ promise: Promise,
1504
+ ) {
1505
+ val activity = reactApplicationContext.getCurrentActivity()
1506
+ if (activity == null) {
1507
+ promise.resolve(
1508
+ Arguments.createMap().apply {
1509
+ putBoolean("success", false)
1510
+ putString("error", "NoActivity")
1511
+ putString("message", "No activity available")
1512
+ },
1513
+ )
1514
+ return
1515
+ }
1516
+
1517
+ try {
1518
+ val uri =
1519
+ androidx.core.content.FileProvider.getUriForFile(
1520
+ reactApplicationContext,
1521
+ "${reactApplicationContext.packageName}.stripe.fileprovider",
1522
+ file,
1523
+ )
1524
+
1525
+ val shareIntent =
1526
+ Intent(Intent.ACTION_SEND).apply {
1527
+ type = "text/csv"
1528
+ putExtra(Intent.EXTRA_STREAM, uri)
1529
+ addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
1530
+ }
1531
+
1532
+ val chooser = Intent.createChooser(shareIntent, "Share CSV Export")
1533
+ activity.startActivity(chooser)
1534
+
1535
+ // Schedule cleanup
1536
+ android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
1537
+ file.delete()
1538
+ }, 3000)
1539
+
1540
+ promise.resolve(
1541
+ Arguments.createMap().apply {
1542
+ putBoolean("success", true)
1543
+ },
1544
+ )
1545
+ } catch (e: Exception) {
1546
+ promise.resolve(
1547
+ Arguments.createMap().apply {
1548
+ putBoolean("success", false)
1549
+ putString("error", "ShareFailed")
1550
+ putString("message", e.message ?: "Unknown error")
1551
+ },
1552
+ )
1553
+ }
1554
+ }
1555
+
1556
+ @ReactMethod
1557
+ override fun authWebViewDeepLinkHandled(
1558
+ id: String,
1559
+ promise: Promise,
1560
+ ) {
1561
+ isAuthWebViewActive = false
1562
+ promise.resolve(null)
1563
+ }
1564
+
1565
+ /**
1566
+ * Store a stripe-connect:// deep link URL for later retrieval.
1567
+ * This prevents the URL from being broadcast to Expo Router.
1568
+ */
1569
+ @ReactMethod
1570
+ override fun storeStripeConnectDeepLink(
1571
+ url: String,
1572
+ promise: Promise,
1573
+ ) {
1574
+ synchronized(pendingUrlsLock) {
1575
+ pendingStripeConnectUrls.add(url)
1576
+ }
1577
+ promise.resolve(null)
1578
+ }
1579
+
1580
+ /**
1581
+ * Poll for pending stripe-connect:// deep link URLs.
1582
+ * Returns all pending URLs and clears the queue.
1583
+ *
1584
+ * URLs are captured by StripeConnectDeepLinkInterceptor Activity, which prevents
1585
+ * Expo Router from receiving the URLs and dismissing the current screen.
1586
+ */
1587
+ @ReactMethod
1588
+ override fun pollAndClearPendingStripeConnectUrls(promise: Promise) {
1589
+ try {
1590
+ val urlsArray = Arguments.createArray()
1591
+
1592
+ // Get URLs from SDK's internal storage (set by StripeConnectDeepLinkInterceptor)
1593
+ val sdkUrls = retrieveAndClearPendingUrls()
1594
+ sdkUrls.forEach { url ->
1595
+ urlsArray.pushString(url)
1596
+ }
1597
+
1598
+ // Legacy: Support old MainActivity pattern (deprecated, will be removed in future version)
1599
+ // This maintains backward compatibility for apps that implemented the manual pattern
1600
+ try {
1601
+ val mainActivityClass = Class.forName("com.stripe.examplestripeconnect.MainActivity")
1602
+ val getPendingUrlsMethod = mainActivityClass.getMethod("getPendingUrls")
1603
+
1604
+ @Suppress("UNCHECKED_CAST")
1605
+ val mainActivityUrls = getPendingUrlsMethod.invoke(null) as? List<String>
1606
+ if (!mainActivityUrls.isNullOrEmpty()) {
1607
+ Log.w(
1608
+ TAG,
1609
+ "Using deprecated MainActivity.getPendingUrls() pattern. " +
1610
+ "This pattern is no longer needed and will be removed in a future version. " +
1611
+ "The SDK now handles stripe-connect:// URLs automatically.",
1612
+ )
1613
+ mainActivityUrls.forEach { url ->
1614
+ urlsArray.pushString(url)
1615
+ }
1616
+ }
1617
+ } catch (e: Exception) {
1618
+ // Expected when not using deprecated pattern - this is fine
1619
+ }
1620
+
1621
+ promise.resolve(urlsArray)
1622
+ } catch (e: Exception) {
1623
+ Log.e(TAG, "Error polling URLs", e)
1624
+ promise.reject("PollError", "Failed to poll pending Stripe Connect URLs: ${e.message}", e)
1625
+ }
1626
+ }
1627
+
1377
1628
  override fun addListener(eventType: String?) {
1378
1629
  // noop, iOS only
1379
1630
  }
@@ -1429,40 +1680,49 @@ class StripeSdkModule(
1429
1680
  }
1430
1681
 
1431
1682
  private var isRecreatingReactActivity = false
1683
+ private var isAuthWebViewActive = false
1432
1684
  private val activityLifecycleCallbacks =
1433
1685
  object : Application.ActivityLifecycleCallbacks {
1434
1686
  override fun onActivityCreated(
1435
1687
  activity: Activity,
1436
1688
  bundle: Bundle?,
1437
1689
  ) {
1438
- if (activity is ReactActivity) {
1690
+ // Only set flag when ReactActivity is actually being recreated (bundle != null)
1691
+ // bundle != null means this is a recreation, not first creation
1692
+ if (activity is ReactActivity && bundle != null) {
1439
1693
  isRecreatingReactActivity = true
1440
1694
  }
1441
- if (isRecreatingReactActivity && activity.javaClass.name.startsWith("com.stripe.android")) {
1695
+
1696
+ // Don't finish Stripe activities during auth webview flow to prevent dismissing the previous screen
1697
+ val isStripeActivity = activity.javaClass.name.startsWith("com.stripe.android")
1698
+ val shouldFinish = isRecreatingReactActivity && isStripeActivity && !isAuthWebViewActive
1699
+
1700
+ if (shouldFinish) {
1442
1701
  activity.finish()
1443
1702
  }
1444
- }
1445
1703
 
1446
- override fun onActivityStarted(activity: Activity) {
1704
+ // Reset flag after finishing Stripe activities
1705
+ if (isRecreatingReactActivity && shouldFinish) {
1706
+ Handler(Looper.getMainLooper()).post {
1707
+ isRecreatingReactActivity = false
1708
+ }
1709
+ }
1447
1710
  }
1448
1711
 
1449
- override fun onActivityResumed(activity: Activity) {
1450
- }
1712
+ override fun onActivityStarted(activity: Activity) {}
1451
1713
 
1452
- override fun onActivityPaused(activity: Activity) {
1453
- }
1714
+ override fun onActivityResumed(activity: Activity) {}
1454
1715
 
1455
- override fun onActivityStopped(activity: Activity) {
1456
- }
1716
+ override fun onActivityPaused(activity: Activity) {}
1717
+
1718
+ override fun onActivityStopped(activity: Activity) {}
1457
1719
 
1458
1720
  override fun onActivitySaveInstanceState(
1459
1721
  activity: Activity,
1460
1722
  bundle: Bundle,
1461
- ) {
1462
- }
1723
+ ) {}
1463
1724
 
1464
- override fun onActivityDestroyed(activity: Activity) {
1465
- }
1725
+ override fun onActivityDestroyed(activity: Activity) {}
1466
1726
  }
1467
1727
 
1468
1728
  /**
@@ -1491,5 +1751,48 @@ class StripeSdkModule(
1491
1751
 
1492
1752
  companion object {
1493
1753
  const val NAME = NativeStripeSdkModuleSpec.NAME
1754
+ private const val TAG = "StripeSdkModule"
1755
+
1756
+ // Read the Stripe Android SDK version from gradle.properties at build time
1757
+ private val STRIPE_ANDROID_SDK_VERSION = BuildConfig.STRIPE_ANDROID_SDK_VERSION
1758
+
1759
+ // Timeout for auth webview fallback (if JavaScript doesn't call authWebViewDeepLinkHandled)
1760
+ private const val AUTH_WEBVIEW_FALLBACK_TIMEOUT_MS = 60_000L
1761
+
1762
+ // SDK-managed storage for pending stripe-connect:// URLs
1763
+ // This is static because deep links can arrive before ReactContext is available
1764
+ private val pendingConnectUrls = mutableListOf<String>()
1765
+ private val urlsLock = Any()
1766
+
1767
+ /**
1768
+ * Store a stripe-connect:// deep link URL.
1769
+ * Called automatically by StripeConnectDeepLinkInterceptor.
1770
+ * Can also be called manually from MainActivity if users implement custom handling.
1771
+ *
1772
+ * This method is thread-safe and can be called from any thread.
1773
+ *
1774
+ * @param url The stripe-connect:// URL to store
1775
+ */
1776
+ @JvmStatic
1777
+ fun storeStripeConnectDeepLink(url: String) {
1778
+ synchronized(urlsLock) {
1779
+ pendingConnectUrls.add(url)
1780
+ }
1781
+ }
1782
+
1783
+ /**
1784
+ * Retrieve and clear pending URLs.
1785
+ * Internal method used by pollAndClearPendingStripeConnectUrls() bridge method.
1786
+ *
1787
+ * @return List of pending stripe-connect:// URLs
1788
+ */
1789
+ @JvmStatic
1790
+ internal fun retrieveAndClearPendingUrls(): List<String> {
1791
+ synchronized(urlsLock) {
1792
+ val urls = pendingConnectUrls.toList()
1793
+ pendingConnectUrls.clear()
1794
+ return urls
1795
+ }
1796
+ }
1494
1797
  }
1495
1798
  }
@@ -60,6 +60,7 @@ class StripeSdkPackage : BaseReactPackage() {
60
60
  AddressSheetViewManager(),
61
61
  EmbeddedPaymentElementViewManager(),
62
62
  NavigationBarManager(),
63
+ PaymentMethodMessagingElementViewManager(),
63
64
  )
64
65
 
65
66
  private fun getOnrampModuleClass(): Class<out NativeModule?> {
@@ -64,6 +64,8 @@ class CustomerSheetManager(
64
64
  val billingConfigParams = arguments.getMap("billingDetailsCollectionConfiguration")
65
65
  val allowsRemovalOfLastSavedPaymentMethod =
66
66
  arguments.getBooleanOr("allowsRemovalOfLastSavedPaymentMethod", true)
67
+ val opensCardScannerAutomatically =
68
+ arguments.getBooleanOr("opensCardScannerAutomatically", false)
67
69
  val paymentMethodOrder = arguments.getStringList("paymentMethodOrder")
68
70
 
69
71
  val appearance =
@@ -83,6 +85,7 @@ class CustomerSheetManager(
83
85
  .preferredNetworks(
84
86
  mapToPreferredNetworks(arguments.getIntegerList("preferredNetworks")),
85
87
  ).allowsRemovalOfLastSavedPaymentMethod(allowsRemovalOfLastSavedPaymentMethod)
88
+ .opensCardScannerAutomatically(opensCardScannerAutomatically)
86
89
  .cardBrandAcceptance(mapToCardBrandAcceptance(arguments))
87
90
 
88
91
  paymentMethodOrder?.let { configuration.paymentMethodOrder(it) }
@@ -58,6 +58,14 @@ class PaymentSheetException(
58
58
  message: String,
59
59
  ) : Exception(message)
60
60
 
61
+ class PaymentMethodMessagingElementAppearanceException(
62
+ message: String,
63
+ ) : Exception(message)
64
+
65
+ class PaymentMethodMessagingElementConfigurationException(
66
+ message: String,
67
+ ) : Exception(message)
68
+
61
69
  internal fun mapError(
62
70
  code: String,
63
71
  message: String?,
@@ -0,0 +1,4 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <paths>
3
+ <cache-path name="stripe_exports" path="stripe-exports/" />
4
+ </paths>
@@ -0,0 +1,36 @@
1
+ /**
2
+ * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
3
+ *
4
+ * Do not edit this file as changes may cause incorrect behavior and will be lost
5
+ * once the code is regenerated.
6
+ *
7
+ * @generated by codegen project: GeneratePropsJavaDelegate.js
8
+ */
9
+
10
+ package com.facebook.react.viewmanagers;
11
+
12
+ import android.view.View;
13
+ import androidx.annotation.Nullable;
14
+ import com.facebook.react.bridge.DynamicFromObject;
15
+ import com.facebook.react.uimanager.BaseViewManager;
16
+ import com.facebook.react.uimanager.BaseViewManagerDelegate;
17
+ import com.facebook.react.uimanager.LayoutShadowNode;
18
+
19
+ public class PaymentMethodMessagingElementViewManagerDelegate<T extends View, U extends BaseViewManager<T, ? extends LayoutShadowNode> & PaymentMethodMessagingElementViewManagerInterface<T>> extends BaseViewManagerDelegate<T, U> {
20
+ public PaymentMethodMessagingElementViewManagerDelegate(U viewManager) {
21
+ super(viewManager);
22
+ }
23
+ @Override
24
+ public void setProperty(T view, String propName, @Nullable Object value) {
25
+ switch (propName) {
26
+ case "appearance":
27
+ mViewManager.setAppearance(view, new DynamicFromObject(value));
28
+ break;
29
+ case "configuration":
30
+ mViewManager.setConfiguration(view, new DynamicFromObject(value));
31
+ break;
32
+ default:
33
+ super.setProperty(view, propName, value);
34
+ }
35
+ }
36
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
3
+ *
4
+ * Do not edit this file as changes may cause incorrect behavior and will be lost
5
+ * once the code is regenerated.
6
+ *
7
+ * @generated by codegen project: GeneratePropsJavaInterface.js
8
+ */
9
+
10
+ package com.facebook.react.viewmanagers;
11
+
12
+ import android.view.View;
13
+ import com.facebook.react.bridge.Dynamic;
14
+
15
+ public interface PaymentMethodMessagingElementViewManagerInterface<T extends View> {
16
+ void setAppearance(T view, Dynamic value);
17
+ void setConfiguration(T view, Dynamic value);
18
+ }
@@ -272,6 +272,22 @@ public abstract class NativeStripeSdkModuleSpec extends ReactContextBaseJavaModu
272
272
  @DoNotStrip
273
273
  public abstract void openAuthenticatedWebView(String id, String url, Promise promise);
274
274
 
275
+ @ReactMethod
276
+ @DoNotStrip
277
+ public abstract void downloadAndShareFile(String url, @Nullable String filename, Promise promise);
278
+
279
+ @ReactMethod
280
+ @DoNotStrip
281
+ public abstract void authWebViewDeepLinkHandled(String id, Promise promise);
282
+
283
+ @ReactMethod
284
+ @DoNotStrip
285
+ public abstract void storeStripeConnectDeepLink(String url, Promise promise);
286
+
287
+ @ReactMethod
288
+ @DoNotStrip
289
+ public abstract void pollAndClearPendingStripeConnectUrls(Promise promise);
290
+
275
291
  @ReactMethod
276
292
  @DoNotStrip
277
293
  public abstract void addListener(String eventType);
@@ -279,4 +295,8 @@ public abstract class NativeStripeSdkModuleSpec extends ReactContextBaseJavaModu
279
295
  @ReactMethod
280
296
  @DoNotStrip
281
297
  public abstract void removeListeners(double count);
298
+
299
+ @ReactMethod
300
+ @DoNotStrip
301
+ public abstract void createRadarSession(Promise promise);
282
302
  }