Conversation
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (10)
📝 WalkthroughWalkthroughThis PR updates Android/iOS Stripe SDK versions, adds native APIs (Radar session, file download/share, deep-link storage/handling), introduces payment-method messaging events, adds card-scanner auto-open and termsDisplay configuration, refactors PaymentSheetManager initialization, and tightens KYC/address mapping. Changes
sequenceDiagram
participant JS as JavaScript
participant RN as StripeSdkModule (Native)
participant Net as Network (OkHttp / URLSession)
participant FS as FileProvider / Cache
participant OS as System Share UI
JS->>RN: downloadAndShareFile(url, filename)
RN->>Net: fetch URL data
Net-->>RN: file bytes
RN->>FS: write cache file & get content URI
RN->>OS: present share sheet with URI
OS-->>RN: completion callback
RN-->>JS: resolve (null or error)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~70 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 14
🧹 Nitpick comments (1)
packages/stripe_android/android/build.gradle (1)
69-69: Align OkHttp versioning strategy with Stripe packages.Line 69 hard-pins
okhttp:5.3.2, whilestripe-androiduses a floating version (23.1.+). Although OkHttp is directly used in StripeSdkModule.kt, hard-pinning it conflicts with the repository's convention of using floating ranges for Stripe-related dependencies. This inconsistency can cause dependency resolution issues if stripe-android's transitive OkHttp differs from 5.3.2. Consider using a floating range (e.g.,5.3.+) or validate compatibility between the pinned version and stripe-android's transitive dependencies.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/stripe_android/android/build.gradle` at line 69, The build hard-pins OkHttp to 'com.squareup.okhttp3:okhttp:5.3.2' which conflicts with the repository's floating-version strategy used by stripe-android and can cause resolution mismatches; update the dependency declaration in the Android Gradle build (the implementation line for com.squareup.okhttp3:okhttp) to use a floating range such as '5.3.+' (or align with the transitive version used by stripe-android) and verify compatibility with StripeSdkModule.kt usage so dependency resolution is consistent across stripe-android and this module.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/stripe_android/android/src/main/AndroidManifest.xml`:
- Around line 29-33: Update the FileProvider authority to a library-specific
namespaced value to avoid collisions: change the manifest provider entry for
android:name="com.reactnativestripesdk.StripeFileProvider" to use an authority
like "${applicationId}.com.reactnativestripesdk.fileprovider" and then update
the corresponding call to FileProvider.getUriForFile(...) in StripeSdkModule.kt
to use the exact same namespaced authority string; ensure both locations
reference the same new authority constant or literal so manifest merges and
runtime URI resolution remain consistent.
- Around line 21-25: The library manifest currently hardcodes the deep-link
scheme via the <data android:scheme="stripe-connect"/> intent-filter which
causes every host app to claim the same scheme; replace this with a manifest
placeholder and require the host app to supply an app-specific scheme (e.g.
generated from its applicationId or passed as a configuration parameter) so the
intent-filter uses that placeholder instead, and ensure the SDK exposes a
configuration entry (or constructor parameter) used when launching the Stripe
Connect auth flow so the host passes the same app-specific redirect URL; update
references to the intent-filter/data android:scheme and any Connect auth
entry-point code to read the provided scheme rather than the hardcoded
"stripe-connect".
In
`@packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/customersheet/CustomerSheetManager.kt`:
- Around line 67-68: The code currently defaults the optional input to false by
using arguments.getBooleanOr("opensCardScannerAutomatically", false) and then
always applying it, which loses the distinction between unset and explicit
false; change the local variable opensCardScannerAutomatically to be nullable
(Boolean?) and populate it by checking for the presence of the key (e.g., if
arguments.hasKey("opensCardScannerAutomatically") then
arguments.getBoolean("opensCardScannerAutomatically") else null), and then only
call the builder/setter (the code on Line 88 that always sets the value) when
opensCardScannerAutomatically != null so the unset case is preserved.
In
`@packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/StripeConnectDeepLinkInterceptorActivity.kt`:
- Around line 57-61: The current use of Intent.FLAG_ACTIVITY_REORDER_TO_FRONT /
FLAG_ACTIVITY_SINGLE_TOP on launchIntent in
StripeConnectDeepLinkInterceptorActivity won't bring the main app task to the
foreground because REORDER_TO_FRONT only works within the same task; replace
those flags with cross-task-safe flags by adding Intent.FLAG_ACTIVITY_NEW_TASK |
Intent.FLAG_ACTIVITY_CLEAR_TOP on the launchIntent (so the system will route to
the existing app task/root and deliver onNewIntent), and remove the
REORDER_TO_FRONT usage; update the code that sets flags on launchIntent to use
NEW_TASK and CLEAR_TOP instead.
In
`@packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/StripeSdkModule.kt`:
- Around line 263-270: The current reuse of PaymentSheetManager in
StripeSdkModule (the if/else around paymentSheetManager and the call to
configure()) leaves native PaymentSheet/FlowController instances created in
PaymentSheetManager.onCreate() stale; always recreate the manager when
configuring or re-initializing (e.g., in initPaymentSheet) so native state is
rebuilt. Replace the reuse path so you instantiate a new
PaymentSheetManager(reactApplicationContext, params, promise) each time and call
registerStripeUIManager(newInstance); if PaymentSheetManager holds native
resources add/Call a dispose/destroy method on the old paymentSheetManager (or
implement one) before replacing it to avoid leaks; ensure configure() is only
used for in-place updates and not relied on for full re-init logic involving
customFlow or intentConfiguration.
- Around line 1576-1583: storeStripeConnectDeepLink currently appends URLs to
the instance-level pendingStripeConnectUrls, but
pollAndClearPendingStripeConnectUrls drains only the companion-level queue via
retrieveAndClearPendingUrls, so URLs never reach the poller; fix by adding the
incoming url into the companion queue used by retrieveAndClearPendingUrls (or
modify retrieveAndClearPendingUrls to also drain the instance queue), e.g. move
the add into the companion's pendingStripeConnectUrls while synchronizing on the
companion's lock (or call a companion method to enqueue), referencing
storeStripeConnectDeepLink, pollAndClearPendingStripeConnectUrls,
retrieveAndClearPendingUrls, pendingStripeConnectUrls and pendingUrlsLock so the
poller reads the same queue the bridge writes to.
- Around line 1696-1714: The flag isRecreatingReactActivity must be reset
whenever a ReactActivity recreation is detected (activity is ReactActivity &&
bundle != null), not only when shouldFinish is true; change the logic so after
you set isRecreatingReactActivity = true you always schedule a reset
(Handler(Looper.getMainLooper()).post { isRecreatingReactActivity = false })
tied to that recreation lifecycle, leaving the existing shouldFinish check for
activity.finish() unchanged; reference isRecreatingReactActivity,
isAuthWebViewActive, shouldFinish, activity (ReactActivity), and the
Handler/Looper post call to locate and update the code.
- Around line 1541-1544: The current Handler.postDelayed({ file.delete() },
3000) deletes the shared cache file after a fixed 3s delay which can race the
receiving app; remove that fixed-timer deletion and instead leave the exported
file in cache, and implement opportunistic cleanup (e.g., a housekeeping method
run on module init or app startup that scans the cache/export directory and
deletes files older than a retention threshold, or use Android Job/WorkManager
to periodically purge aged files). Locate the Handler.postDelayed block (the
file.delete() call) in StripeSdkModule.kt, remove it and add a cleanup routine
that deletes files by age or on startup rather than using a fixed short delay.
In `@packages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe`
Sdk/Mappers.swift:
- Around line 1290-1299: The intValue(_:) helper currently accepts any NSNumber
(including booleans and floats) which lets invalid KYC dates slip through;
modify intValue(_ value: Any?) so it returns nil for NSNumbers that are actually
booleans or that have a fractional component: explicitly reject value as Bool
(or detect CFBoolean via CFGetTypeID) and for numeric NSNumbers verify that
number.doubleValue.truncatingRemainder(dividingBy: 1) == 0 before returning
number.intValue; otherwise return nil so the caller (e.g.,
DateOfBirth/dateOfBirth parsing) will trigger invalidField("dateOfBirth").
In `@packages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe`
Sdk/StripeSdkImpl.swift:
- Around line 1947-1956: The dataTask completion handler currently ignores the
URLResponse and may treat HTTP 4xx/5xx responses as successful; update the
URLSession.shared.dataTask(with: url) { [weak self] data, response, error in ...
} closures to cast response to HTTPURLResponse and check response.statusCode is
in 200...299 before writing the file—if the status is outside that range, call
resolve with a meaningful error (e.g., "HTTPError" and the statusCode and/or
server body message) and do not save or present the share sheet; apply the same
fix to the other identical dataTask block around the referenced 1966-1989
region.
- Around line 1991-2022: presentShareSheet currently grabs
RCTKeyWindow()?.rootViewController and calls present(...) which can fail if the
root is already presenting; instead obtain a presenter via
findViewControllerPresenter(...) and present the UIActivityViewController on
that presenter. Update presentShareSheet to: use findViewControllerPresenter()
to get a presenting UIViewController (fall back to cleaning up file and calling
completion(false) if nil), set popover.sourceView and sourceRect to the
presenter's view when popoverPresentationController exists, and call
presenter.present(activityVC, animated: true). Also ensure the cleanup of the
temp file and calling completion(completed) remain in
activityVC.completionWithItemsHandler.
In `@packages/stripe_platform_interface/lib/src/models/customer_sheet.dart`:
- Around line 74-80: The deprecated factory includes the bool?
opensCardScannerAutomatically field but the .adapter and .session factories are
missing it; update both factory signatures (.adapter and .session) to accept a
bool? opensCardScannerAutomatically parameter and forward that value into the
created instance (the private constructor / underlying type used by those
factories, e.g., _CustomerSheetInitParamsDeprecated or the internal
_CustomerSheetInitParams) so the field is set consistently across all factories.
Ensure the parameter name and nullable type match the deprecated factory and
that it's passed through to wherever other similar params
(allowsRemovalOfLastSavedPaymentMethod, preferredNetworks, cardBrandAcceptance)
are forwarded.
In `@packages/stripe_platform_interface/lib/src/models/payment_sheet.dart`:
- Around line 139-145: The docstring for the field termsDisplay describes a
per-payment-method map but the field is declared as a single TermsDisplay?;
change the field to Map<String, TermsDisplay>? to match the intended map-based
design (and the Android PaymentElementConfig.kt/PaymentSheet.TermsDisplay
usage), update the doc comment to match snake_case keys, and adjust any
constructors/serialization/deserialization and equality/copy logic that
reference termsDisplay so they handle a nullable Map<String, TermsDisplay>
instead of a single enum value; ensure any platform/channel conversion code uses
PaymentMethod.Type keys mapped to the same snake_case strings.
In
`@packages/stripe_platform_interface/lib/src/models/payment_sheet.freezed.dart`:
- Around line 92-98: The generated model exposes termsDisplay as a single
TermsDisplay? but it must be a per-payment-method map; update the source model's
termsDisplay declaration (the field named termsDisplay and its type in the
PaymentSheet model) to use Map<String, TermsDisplay>? (or the appropriate
nullable map type) so serialization emits {"card":"automatic"} style objects,
then regenerate the freezed model (the generated payment_sheet.freezed.dart will
update) and verify the native bridge consumers (TermsDisplay enum and
termsDisplay field) receive a map; if a global enum was intended instead, align
docs/native code accordingly.
---
Nitpick comments:
In `@packages/stripe_android/android/build.gradle`:
- Line 69: The build hard-pins OkHttp to 'com.squareup.okhttp3:okhttp:5.3.2'
which conflicts with the repository's floating-version strategy used by
stripe-android and can cause resolution mismatches; update the dependency
declaration in the Android Gradle build (the implementation line for
com.squareup.okhttp3:okhttp) to use a floating range such as '5.3.+' (or align
with the transitive version used by stripe-android) and verify compatibility
with StripeSdkModule.kt usage so dependency resolution is consistent across
stripe-android and this module.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: d69e9b41-c898-4925-9683-db99f2a9f214
📒 Files selected for processing (40)
example/android/gradle/wrapper/gradle-wrapper.propertiesexample/android/settings.gradleexample/lib/screens/card_payments/webhook_payment_screen.dartexample/lib/screens/payment_sheet/payment_sheet_screen.dartpackages/stripe_android/android/build.gradlepackages/stripe_android/android/gradle/wrapper/gradle-wrapper.propertiespackages/stripe_android/android/src/main/AndroidManifest.xmlpackages/stripe_android/android/src/main/kotlin/com/facebook/react/bridge/Promise.javapackages/stripe_android/android/src/main/kotlin/com/facebook/react/bridge/ReactApplicationContext.javapackages/stripe_android/android/src/main/kotlin/com/facebook/react/bridge/ReactContext.ktpackages/stripe_android/android/src/main/kotlin/com/flutter/stripe/StripeSdkModuleExtensions.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/EventEmitterCompat.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/NativeStripeSdkModuleSpec.javapackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/PaymentElementConfig.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/PaymentSheetAppearance.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/PaymentSheetManager.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/StripeConnectDeepLinkInterceptorActivity.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/StripeFileProvider.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/StripeSdkModule.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/customersheet/CustomerSheetManager.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/utils/Errors.ktpackages/stripe_android/android/src/main/res/xml/file_paths.xmlpackages/stripe_ios/ios/stripe_ios.podspecpackages/stripe_ios/ios/stripe_ios/Package.swiftpackages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/CustomerSheet/CustomerSheetUtils.swiftpackages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/Mappers.swiftpackages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/StripeSdkEmitter.swiftpackages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/StripeSdkImpl+CustomerSheet.swiftpackages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/StripeSdkImpl+Embedded.swiftpackages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/StripeSdkImpl+PaymentSheet.swiftpackages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/StripeSdkImpl.swiftpackages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/StripePlugin.swiftpackages/stripe_ios/ios/stripe_ios/Sources/stripe_objc/Stripe Sdk/StripeSdkEventEmitterCompat.hpackages/stripe_ios/ios/stripe_ios/Sources/stripe_objc/Stripe Sdk/StripeSdkEventEmitterCompat.mpackages/stripe_platform_interface/lib/src/models/customer_sheet.dartpackages/stripe_platform_interface/lib/src/models/customer_sheet.freezed.dartpackages/stripe_platform_interface/lib/src/models/customer_sheet.g.dartpackages/stripe_platform_interface/lib/src/models/payment_sheet.dartpackages/stripe_platform_interface/lib/src/models/payment_sheet.freezed.dartpackages/stripe_platform_interface/lib/src/models/payment_sheet.g.dart
| <intent-filter> | ||
| <action android:name="android.intent.action.VIEW"/> | ||
| <category android:name="android.intent.category.DEFAULT"/> | ||
| <category android:name="android.intent.category.BROWSABLE"/> | ||
| <data android:scheme="stripe-connect"/> |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Find the AndroidManifest.xml file for stripe_android
find . -path "*stripe_android*" -name "AndroidManifest.xml" -type fRepository: flutter-stripe/flutter_stripe
Length of output: 135
🏁 Script executed:
# Check the content of the manifest file to see the intent-filter declaration
cat -n packages/stripe_android/android/src/main/AndroidManifest.xml | head -40Repository: flutter-stripe/flutter_stripe
Length of output: 1997
🏁 Script executed:
# Search for any scheme-related configuration or documentation
rg "stripe-connect|scheme.*config|deep.?link" --type java --type kotlin -iRepository: flutter-stripe/flutter_stripe
Length of output: 5601
Avoid hardcoding a shared stripe-connect scheme in the library manifest.
The fixed stripe-connect:// intent-filter is merged into every host app. When multiple apps using flutter_stripe are installed, they all claim the same deep-link scheme. Android may show a chooser or route the Connect return to the wrong app. Use an app-specific scheme—either by accepting it as a configuration parameter or generating it from the host app's applicationId—and pass that URL to the Stripe Connect auth flow.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/stripe_android/android/src/main/AndroidManifest.xml` around lines 21
- 25, The library manifest currently hardcodes the deep-link scheme via the
<data android:scheme="stripe-connect"/> intent-filter which causes every host
app to claim the same scheme; replace this with a manifest placeholder and
require the host app to supply an app-specific scheme (e.g. generated from its
applicationId or passed as a configuration parameter) so the intent-filter uses
that placeholder instead, and ensure the SDK exposes a configuration entry (or
constructor parameter) used when launching the Stripe Connect auth flow so the
host passes the same app-specific redirect URL; update references to the
intent-filter/data android:scheme and any Connect auth entry-point code to read
the provided scheme rather than the hardcoded "stripe-connect".
| <provider | ||
| android:name="com.reactnativestripesdk.StripeFileProvider" | ||
| android:authorities="${applicationId}.fileprovider" | ||
| android:exported="false" | ||
| android:grantUriPermissions="true"> |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's explore the repository structure
find . -name "AndroidManifest.xml" | head -20Repository: flutter-stripe/flutter_stripe
Length of output: 292
🏁 Script executed:
# Also search for FileProvider-related code
rg -i "fileprovider|FileProvider" --type xml --type kt --type java | head -50Repository: flutter-stripe/flutter_stripe
Length of output: 99
🏁 Script executed:
# Check if there are any other FileProvider declarations
rg -i "android:authorities" --type xmlRepository: flutter-stripe/flutter_stripe
Length of output: 197
🏁 Script executed:
# Read the AndroidManifest.xml file
cat -n packages/stripe_android/android/src/main/AndroidManifest.xmlRepository: flutter-stripe/flutter_stripe
Length of output: 1997
🏁 Script executed:
# Search for FileProvider.getUriForFile usage in Java files
rg "FileProvider.getUriForFile|getUriForFile" -t java -B 3 -A 3Repository: flutter-stripe/flutter_stripe
Length of output: 55
🏁 Script executed:
# Check the example app manifests for FileProvider declarations
rg -i "android:authorities" --type xml -B 2 -A 2Repository: flutter-stripe/flutter_stripe
Length of output: 615
🏁 Script executed:
# Search for any Java files in the stripe_android module
find packages/stripe_android -name "*.java" | head -20Repository: flutter-stripe/flutter_stripe
Length of output: 1988
🏁 Script executed:
# Find Kotlin files in stripe_android
find packages/stripe_android -name "*.kt" -type f | head -20Repository: flutter-stripe/flutter_stripe
Length of output: 2046
🏁 Script executed:
# Search for FileProvider in Kotlin files
rg "FileProvider|fileprovider" packages/stripe_android -i -B 2 -A 2Repository: flutter-stripe/flutter_stripe
Length of output: 3783
🏁 Script executed:
# Check example app manifest to compare
cat -n example/android/app/src/main/AndroidManifest.xml | grep -A 10 -B 2 "provider\|authorities"Repository: flutter-stripe/flutter_stripe
Length of output: 55
Namespace the FileProvider authority to avoid collisions with other libraries.
The authority ${applicationId}.fileprovider is a common convention that multiple libraries and host apps use. This causes manifest merge conflicts or installation failures when multiple FileProviders declare the same authority. While the custom StripeFileProvider class name helps distinguish the provider in the manifest, the authority string itself must be unique. Use a library-specific suffix like ${applicationId}.com.reactnativestripesdk.fileprovider in both the manifest (line 31) and the corresponding FileProvider.getUriForFile(...) call in StripeSdkModule.kt.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/stripe_android/android/src/main/AndroidManifest.xml` around lines 29
- 33, Update the FileProvider authority to a library-specific namespaced value
to avoid collisions: change the manifest provider entry for
android:name="com.reactnativestripesdk.StripeFileProvider" to use an authority
like "${applicationId}.com.reactnativestripesdk.fileprovider" and then update
the corresponding call to FileProvider.getUriForFile(...) in StripeSdkModule.kt
to use the exact same namespaced authority string; ensure both locations
reference the same new authority constant or literal so manifest merges and
runtime URI resolution remain consistent.
| val opensCardScannerAutomatically = | ||
| arguments.getBooleanOr("opensCardScannerAutomatically", false) |
There was a problem hiding this comment.
Preserve nullable semantics for opensCardScannerAutomatically.
Lines 67–68 default missing values to false, and Line 88 always sets it. Since the input field is optional, this removes the distinction between “unset” and “explicit false”.
Suggested fix
- val opensCardScannerAutomatically =
- arguments.getBooleanOr("opensCardScannerAutomatically", false)
+ val opensCardScannerAutomatically =
+ if (arguments.hasKey("opensCardScannerAutomatically")) {
+ arguments.getBoolean("opensCardScannerAutomatically")
+ } else {
+ null
+ }
...
- .opensCardScannerAutomatically(opensCardScannerAutomatically)
+ .apply {
+ opensCardScannerAutomatically?.let { opensCardScannerAutomatically(it) }
+ }Also applies to: 88-88
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/customersheet/CustomerSheetManager.kt`
around lines 67 - 68, The code currently defaults the optional input to false by
using arguments.getBooleanOr("opensCardScannerAutomatically", false) and then
always applying it, which loses the distinction between unset and explicit
false; change the local variable opensCardScannerAutomatically to be nullable
(Boolean?) and populate it by checking for the presence of the key (e.g., if
arguments.hasKey("opensCardScannerAutomatically") then
arguments.getBoolean("opensCardScannerAutomatically") else null), and then only
call the builder/setter (the code on Line 88 that always sets the value) when
opensCardScannerAutomatically != null so the unset case is preserved.
| if (launchIntent != null) { | ||
| // Flags to bring existing task to front without recreating activities | ||
| launchIntent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT) | ||
| launchIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP) | ||
| startActivity(launchIntent) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's examine the StripeConnectDeepLinkInterceptorActivity.kt file
find . -name "StripeConnectDeepLinkInterceptorActivity.kt" -type fRepository: flutter-stripe/flutter_stripe
Length of output: 191
🏁 Script executed:
# Look for AndroidManifest.xml in the stripe_android package
find . -path "*/packages/stripe_android/*" -name "AndroidManifest.xml" -type fRepository: flutter-stripe/flutter_stripe
Length of output: 135
🏁 Script executed:
# Let's also check the directory structure to understand the project layout
ls -la packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/ 2>/dev/null | head -20Repository: flutter-stripe/flutter_stripe
Length of output: 1449
🏁 Script executed:
# Read the StripeConnectDeepLinkInterceptorActivity.kt file to see the full implementation
cat -n ./packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/StripeConnectDeepLinkInterceptorActivity.ktRepository: flutter-stripe/flutter_stripe
Length of output: 3666
🏁 Script executed:
# Read the AndroidManifest.xml to check the activity declaration
cat -n ./packages/stripe_android/android/src/main/AndroidManifest.xmlRepository: flutter-stripe/flutter_stripe
Length of output: 1997
REORDER_TO_FRONT won't work across tasks here.
StripeConnectDeepLinkInterceptorActivity is declared as singleTask in the manifest. When Custom Tabs redirects back via a stripe-connect:// deep link, this interceptor launches in a new task (as documented in the code comments). FLAG_ACTIVITY_REORDER_TO_FRONT only reorders activities within the current task—it cannot switch to the main app's task. This can leave users stranded in the Custom Tab instead of returning them to the in-app flow after Connect completes.
Suggested fix
- launchIntent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)
- launchIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
+ launchIntent.addFlags(
+ Intent.FLAG_ACTIVITY_NEW_TASK or
+ Intent.FLAG_ACTIVITY_CLEAR_TOP or
+ Intent.FLAG_ACTIVITY_SINGLE_TOP
+ )🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/StripeConnectDeepLinkInterceptorActivity.kt`
around lines 57 - 61, The current use of Intent.FLAG_ACTIVITY_REORDER_TO_FRONT /
FLAG_ACTIVITY_SINGLE_TOP on launchIntent in
StripeConnectDeepLinkInterceptorActivity won't bring the main app task to the
foreground because REORDER_TO_FRONT only works within the same task; replace
those flags with cross-task-safe flags by adding Intent.FLAG_ACTIVITY_NEW_TASK |
Intent.FLAG_ACTIVITY_CLEAR_TOP on the launchIntent (so the system will route to
the existing app task/root and deliver onNewIntent), and remove the
REORDER_TO_FRONT usage; update the code that sets flags on launchIntent to use
NEW_TASK and CLEAR_TOP instead.
| if (paymentSheetManager != null) { | ||
| paymentSheetManager?.configure(params, promise) | ||
| } else { | ||
| paymentSheetManager = | ||
| PaymentSheetManager(reactApplicationContext, params, promise).also { | ||
| registerStripeUIManager(it) | ||
| } | ||
| } |
There was a problem hiding this comment.
Keep recreating PaymentSheetManager until re-init can rebuild native state.
This reuse path leaves the first native PaymentSheet/FlowController instance alive. In packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/PaymentSheetManager.kt, those objects are still created only once in onCreate(), while configure() only updates config fields. A second initPaymentSheet that flips customFlow, switches to or from intentConfiguration, or retries after an init-time error can therefore reuse stale or null native state and present the wrong flow or nothing at all.
Suggested quick fix
- if (paymentSheetManager != null) {
- paymentSheetManager?.configure(params, promise)
- } else {
- paymentSheetManager =
- PaymentSheetManager(reactApplicationContext, params, promise).also {
- registerStripeUIManager(it)
- }
- }
+ unregisterStripeUIManager(paymentSheetManager)
+ paymentSheetManager =
+ PaymentSheetManager(reactApplicationContext, params, promise).also {
+ registerStripeUIManager(it)
+ }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/StripeSdkModule.kt`
around lines 263 - 270, The current reuse of PaymentSheetManager in
StripeSdkModule (the if/else around paymentSheetManager and the call to
configure()) leaves native PaymentSheet/FlowController instances created in
PaymentSheetManager.onCreate() stale; always recreate the manager when
configuring or re-initializing (e.g., in initPaymentSheet) so native state is
rebuilt. Replace the reuse path so you instantiate a new
PaymentSheetManager(reactApplicationContext, params, promise) each time and call
registerStripeUIManager(newInstance); if PaymentSheetManager holds native
resources add/Call a dispose/destroy method on the old paymentSheetManager (or
implement one) before replacing it to avoid leaks; ensure configure() is only
used for in-place updates and not relied on for full re-init logic involving
customFlow or intentConfiguration.
| let task = URLSession.shared.dataTask(with: url) { [weak self] data, _, error in | ||
| if let error = error { | ||
| resolve(["success": false, "error": "NetworkError", "message": error.localizedDescription]) | ||
| return | ||
| } | ||
|
|
||
| guard let data = data else { | ||
| resolve(["success": false, "error": "NoData"]) | ||
| return | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -type f -name "StripeSdkImpl.swift" | head -20Repository: flutter-stripe/flutter_stripe
Length of output: 159
🏁 Script executed:
# Get the file location
FILE_PATH=$(find . -type f -name "StripeSdkImpl.swift")
if [ -n "$FILE_PATH" ]; then
# Count total lines
TOTAL_LINES=$(wc -l < "$FILE_PATH")
echo "File: $FILE_PATH"
echo "Total lines: $TOTAL_LINES"
# Show lines 1945-1960 (context around 1947-1956)
echo "=== Lines 1945-1960 ==="
sed -n '1945,1960p' "$FILE_PATH" | cat -n | sed 's/^[[:space:]]*1945/1945/' | awk '{printf "%d: %s\n", NR+1944, $0}'
# Show lines 1964-1992 (context around 1966-1989)
echo ""
echo "=== Lines 1964-1992 ==="
sed -n '1964,1992p' "$FILE_PATH" | cat -n | awk '{printf "%d: %s\n", NR+1963, $0}'
else
echo "File not found"
fiRepository: flutter-stripe/flutter_stripe
Length of output: 2530
🏁 Script executed:
FILE_PATH="./packages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/StripeSdkImpl.swift"
# Search for dataTask patterns and how response is handled elsewhere
echo "=== Searching for other dataTask patterns ==="
rg "dataTask\(with:" "$FILE_PATH" -A 10 -B 2Repository: flutter-stripe/flutter_stripe
Length of output: 541
🏁 Script executed:
# Also check if HTTPURLResponse is used anywhere in the file
FILE_PATH="./packages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/StripeSdkImpl.swift"
echo "=== Checking for HTTPURLResponse usage ==="
rg "HTTPURLResponse|statusCode" "$FILE_PATH" -B 2 -A 2Repository: flutter-stripe/flutter_stripe
Length of output: 798
Validate HTTP status codes before saving the downloaded file.
URLSession transport errors (in the error parameter) only cover connection-level failures; HTTP 4xx/5xx responses arrive with valid data and nil error. This code will currently write the server error body to disk, present the share sheet, and resolve as success. The response parameter is ignored.
Suggested fix
- let task = URLSession.shared.dataTask(with: url) { [weak self] data, _, error in
+ let task = URLSession.shared.dataTask(with: url) { [weak self] data, response, error in
if let error = error {
resolve(["success": false, "error": "NetworkError", "message": error.localizedDescription])
return
}
+
+ if let http = response as? HTTPURLResponse, !(200...299).contains(http.statusCode) {
+ resolve(["success": false, "error": "NetworkError", "message": "HTTP \(http.statusCode)"])
+ return
+ }
guard let data = data else {
resolve(["success": false, "error": "NoData"])
return
}Also applies to: 1966-1989
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe`
Sdk/StripeSdkImpl.swift around lines 1947 - 1956, The dataTask completion
handler currently ignores the URLResponse and may treat HTTP 4xx/5xx responses
as successful; update the URLSession.shared.dataTask(with: url) { [weak self]
data, response, error in ... } closures to cast response to HTTPURLResponse and
check response.statusCode is in 200...299 before writing the file—if the status
is outside that range, call resolve with a meaningful error (e.g., "HTTPError"
and the statusCode and/or server body message) and do not save or present the
share sheet; apply the same fix to the other identical dataTask block around the
referenced 1966-1989 region.
| private func presentShareSheet(fileURL: URL, completion: @escaping (Bool) -> Void) { | ||
| guard let rootViewController = RCTKeyWindow()?.rootViewController else { | ||
| // Clean up temp file | ||
| try? FileManager.default.removeItem(at: fileURL) | ||
| completion(false) | ||
| return | ||
| } | ||
|
|
||
| let activityVC = UIActivityViewController( | ||
| activityItems: [fileURL], | ||
| applicationActivities: nil | ||
| ) | ||
|
|
||
| // iPad support | ||
| if let popover = activityVC.popoverPresentationController { | ||
| popover.sourceView = rootViewController.view | ||
| popover.sourceRect = CGRect( | ||
| x: rootViewController.view.bounds.midX, | ||
| y: rootViewController.view.bounds.midY, | ||
| width: 0, | ||
| height: 0 | ||
| ) | ||
| popover.permittedArrowDirections = [] | ||
| } | ||
|
|
||
| activityVC.completionWithItemsHandler = { _, completed, _, _ in | ||
| // Clean up temp file after sharing is complete | ||
| try? FileManager.default.removeItem(at: fileURL) | ||
| completion(completed) | ||
| } | ||
|
|
||
| rootViewController.present(activityVC, animated: true) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's locate the StripeSdkImpl.swift file
fd -t f "StripeSdkImpl.swift"Repository: flutter-stripe/flutter_stripe
Length of output: 157
🏁 Script executed:
# Search for findViewControllerPresenter usage in the file
rg "findViewControllerPresenter" packages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe\ Sdk/StripeSdkImpl.swift -A 3 -B 3Repository: flutter-stripe/flutter_stripe
Length of output: 4462
🏁 Script executed:
# Check the implementation of findViewControllerPresenter
rg "func findViewControllerPresenter" packages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe\ Sdk/StripeSdkImpl.swift -A 20Repository: flutter-stripe/flutter_stripe
Length of output: 1065
🏁 Script executed:
# Look for other calls to present() to see if there's a pattern of using findViewControllerPresenter
rg "\.present\(" packages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe\ Sdk/StripeSdkImpl.swift -B 5Repository: flutter-stripe/flutter_stripe
Length of output: 1347
🏁 Script executed:
# Get the exact context around lines 1991-2022 in the file
sed -n '1985,2025p' packages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe\ Sdk/StripeSdkImpl.swift | cat -nRepository: flutter-stripe/flutter_stripe
Length of output: 1827
Use findViewControllerPresenter() to present the share sheet.
The file already uses findViewControllerPresenter(...) extensively for presenting view controllers (Apple Pay, PaymentSheet, authentication flows) because rootViewController may already be presenting another screen. Calling present() directly on the root controller here will fail silently if it's already presenting, leaving the completion handler unresolved.
Suggested fix
- rootViewController.present(activityVC, animated: true)
+ let presenter = findViewControllerPresenter(from: rootViewController)
+ presenter.present(activityVC, animated: true)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| private func presentShareSheet(fileURL: URL, completion: @escaping (Bool) -> Void) { | |
| guard let rootViewController = RCTKeyWindow()?.rootViewController else { | |
| // Clean up temp file | |
| try? FileManager.default.removeItem(at: fileURL) | |
| completion(false) | |
| return | |
| } | |
| let activityVC = UIActivityViewController( | |
| activityItems: [fileURL], | |
| applicationActivities: nil | |
| ) | |
| // iPad support | |
| if let popover = activityVC.popoverPresentationController { | |
| popover.sourceView = rootViewController.view | |
| popover.sourceRect = CGRect( | |
| x: rootViewController.view.bounds.midX, | |
| y: rootViewController.view.bounds.midY, | |
| width: 0, | |
| height: 0 | |
| ) | |
| popover.permittedArrowDirections = [] | |
| } | |
| activityVC.completionWithItemsHandler = { _, completed, _, _ in | |
| // Clean up temp file after sharing is complete | |
| try? FileManager.default.removeItem(at: fileURL) | |
| completion(completed) | |
| } | |
| rootViewController.present(activityVC, animated: true) | |
| private func presentShareSheet(fileURL: URL, completion: `@escaping` (Bool) -> Void) { | |
| guard let rootViewController = RCTKeyWindow()?.rootViewController else { | |
| // Clean up temp file | |
| try? FileManager.default.removeItem(at: fileURL) | |
| completion(false) | |
| return | |
| } | |
| let activityVC = UIActivityViewController( | |
| activityItems: [fileURL], | |
| applicationActivities: nil | |
| ) | |
| // iPad support | |
| if let popover = activityVC.popoverPresentationController { | |
| popover.sourceView = rootViewController.view | |
| popover.sourceRect = CGRect( | |
| x: rootViewController.view.bounds.midX, | |
| y: rootViewController.view.bounds.midY, | |
| width: 0, | |
| height: 0 | |
| ) | |
| popover.permittedArrowDirections = [] | |
| } | |
| activityVC.completionWithItemsHandler = { _, completed, _, _ in | |
| // Clean up temp file after sharing is complete | |
| try? FileManager.default.removeItem(at: fileURL) | |
| completion(completed) | |
| } | |
| let presenter = findViewControllerPresenter(from: rootViewController) | |
| presenter.present(activityVC, animated: true) | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe`
Sdk/StripeSdkImpl.swift around lines 1991 - 2022, presentShareSheet currently
grabs RCTKeyWindow()?.rootViewController and calls present(...) which can fail
if the root is already presenting; instead obtain a presenter via
findViewControllerPresenter(...) and present the UIActivityViewController on
that presenter. Update presentShareSheet to: use findViewControllerPresenter()
to get a presenting UIViewController (fall back to cleaning up file and calling
completion(false) if nil), set popover.sourceView and sourceRect to the
presenter's view when popoverPresentationController exists, and call
presenter.present(activityVC, animated: true). Also ensure the cleanup of the
temp file and calling completion(completed) remain in
activityVC.completionWithItemsHandler.
|
|
||
| /// By default, CustomerSheet offers a card scan button within the new card entry form. | ||
| /// When opensCardScannerAutomatically is set to true, | ||
| /// the card entry form will initialize with the card scanner already open. | ||
| /// Defaults to false. | ||
| bool? opensCardScannerAutomatically, | ||
| }) = _CustomerSheetInitParamsDeprecated; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check if opensCardScannerAutomatically is used in native implementations and which init paths support it
rg -n "opensCardScannerAutomatically" --type kotlin --type swift -C2Repository: flutter-stripe/flutter_stripe
Length of output: 8013
🏁 Script executed:
cat -n packages/stripe_platform_interface/lib/src/models/customer_sheet.dart | head -250Repository: flutter-stripe/flutter_stripe
Length of output: 14611
Add opensCardScannerAutomatically to the .adapter and .session factories.
The parameter is present only in the deprecated factory but missing from both .adapter (line 83) and .session (line 147) factories. Since the deprecated factory directs users to use these newer factories instead, and native implementations support this parameter across all initialization contexts, it should be added to both factories for consistency. All other comparable parameters (allowsRemovalOfLastSavedPaymentMethod, preferredNetworks, cardBrandAcceptance) are present in all three factories.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/stripe_platform_interface/lib/src/models/customer_sheet.dart` around
lines 74 - 80, The deprecated factory includes the bool?
opensCardScannerAutomatically field but the .adapter and .session factories are
missing it; update both factory signatures (.adapter and .session) to accept a
bool? opensCardScannerAutomatically parameter and forward that value into the
created instance (the private constructor / underlying type used by those
factories, e.g., _CustomerSheetInitParamsDeprecated or the internal
_CustomerSheetInitParams) so the field is set consistently across all factories.
Ensure the parameter name and nullable type match the deprecated factory and
that it's passed through to wherever other similar params
(allowsRemovalOfLastSavedPaymentMethod, preferredNetworks, cardBrandAcceptance)
are forwarded.
| /// A map of payment method types to their terms display configuration. | ||
| /// Controls whether legal agreements (e.g. card mandate disclaimers) are shown for each payment method type. | ||
| /// Keys are snake_case payment method type strings (e.g. "card", "us_bank_account"). | ||
| /// See https://docs.stripe.com/api/payment_methods/object#payment_method_object-type for the full list of values. | ||
| /// Values are `TermsDisplay.AUTOMATIC` or `TermsDisplay.NEVER`. | ||
| /// If not set, defaults to `TermsDisplay.AUTOMATIC` for all payment method types. | ||
| TermsDisplay? termsDisplay, |
There was a problem hiding this comment.
Documentation mismatch with field type.
The documentation describes termsDisplay as "A map of payment method types to their terms display configuration" with snake_case keys, but the actual field type is TermsDisplay? (a single enum value), not a Map<String, TermsDisplay>?.
Either update the documentation to reflect the simpler single-value behavior, or change the type to a map if per-payment-method configuration is intended. The Android implementation in PaymentElementConfig.kt returns Map<PaymentMethod.Type, PaymentSheet.TermsDisplay>?, suggesting the map-based approach may be the intended design.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/stripe_platform_interface/lib/src/models/payment_sheet.dart` around
lines 139 - 145, The docstring for the field termsDisplay describes a
per-payment-method map but the field is declared as a single TermsDisplay?;
change the field to Map<String, TermsDisplay>? to match the intended map-based
design (and the Android PaymentElementConfig.kt/PaymentSheet.TermsDisplay
usage), update the doc comment to match snake_case keys, and adjust any
constructors/serialization/deserialization and equality/copy logic that
reference termsDisplay so they handle a nullable Map<String, TermsDisplay>
instead of a single enum value; ensure any platform/channel conversion code uses
PaymentMethod.Type keys mapped to the same snake_case strings.
| bool? get opensCardScannerAutomatically;/// A map of payment method types to their terms display configuration. | ||
| /// Controls whether legal agreements (e.g. card mandate disclaimers) are shown for each payment method type. | ||
| /// Keys are snake_case payment method type strings (e.g. "card", "us_bank_account"). | ||
| /// See https://docs.stripe.com/api/payment_methods/object#payment_method_object-type for the full list of values. | ||
| /// Values are `TermsDisplay.AUTOMATIC` or `TermsDisplay.NEVER`. | ||
| /// If not set, defaults to `TermsDisplay.AUTOMATIC` for all payment method types. | ||
| TermsDisplay? get termsDisplay; |
There was a problem hiding this comment.
termsDisplay has the wrong shape for the native bridge.
The generated model exposes termsDisplay as TermsDisplay?, but both the docs here and the native parsers expect a per-payment-method map. With the current type, Dart will emit "termsDisplay": "automatic" instead of "termsDisplay": {"card": "automatic"}, so this new option will not be consumed correctly by Android (packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/PaymentElementConfig.kt:82-94) or iOS (packages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/StripeSdkImpl+PaymentSheet.swift:488-502). Because this file is generated, please fix the source model in packages/stripe_platform_interface/lib/src/models/payment_sheet.dart to use a map type (or align native/docs if a global enum was intended) and regenerate.
Suggested source-level fix
--- a/packages/stripe_platform_interface/lib/src/models/payment_sheet.dart
+++ b/packages/stripe_platform_interface/lib/src/models/payment_sheet.dart
@@
- final TermsDisplay? termsDisplay;
+ final Map<String, TermsDisplay>? termsDisplay;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/stripe_platform_interface/lib/src/models/payment_sheet.freezed.dart`
around lines 92 - 98, The generated model exposes termsDisplay as a single
TermsDisplay? but it must be a per-payment-method map; update the source model's
termsDisplay declaration (the field named termsDisplay and its type in the
PaymentSheet model) to use Map<String, TermsDisplay>? (or the appropriate
nullable map type) so serialization emits {"card":"automatic"} style objects,
then regenerate the freezed model (the generated payment_sheet.freezed.dart will
update) and verify the native bridge consumers (TermsDisplay enum and
termsDisplay field) receive a map; if a global enum was intended instead, align
docs/native code accordingly.
There was a problem hiding this comment.
Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!
@jonasbark let's integrate this quickly so the blocker with the new xcode is gone I created #2378 because we need to add some native views and plugin methods.
Summary by CodeRabbit
New Features
Updates