Skip to content

Sync#2336

Merged
remonh87 merged 11 commits into
mainfrom
sync
Feb 18, 2026
Merged

Sync#2336
remonh87 merged 11 commits into
mainfrom
sync

Conversation

@remonh87

@remonh87 remonh87 commented Feb 16, 2026

Copy link
Copy Markdown
Member

Summary by CodeRabbit

  • New Features

    • Card funding filtering to restrict payment cards by debit/credit/prepaid.
    • Link added as a supported payment method.
    • Connected account ID support for Financial Connections flows.
  • Improvements

    • Upgraded Stripe SDKs (iOS 25.6.0, Android 22.7.x).
    • Async image processing and lifecycle improvements for snappier payment UIs.
    • Presentation flow tweaks for smoother modal rendering.
  • Bug Fixes

    • More reliable modal presentation and close-button behavior.

@remonh87 remonh87 requested a review from jonasbark February 16, 2026 16:45
@coderabbitai

coderabbitai Bot commented Feb 16, 2026

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Deleted iOS SwiftPM lock entries; bumped native Stripe versions; added card-funding filtering models and plumbing; added connectedAccountId support for Financial Connections; migrated Android image/icon flows to coroutine-based async base64 conversion and lifecycle/UI tweaks; added Dart enum/field updates.

Changes

Cohort / File(s) Summary
iOS Package Resolution Cleanup
example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved, example/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved
Removed SwiftPM resolved lock content, deleting pinned stripe-ios-spm entry and resolved metadata.
Example App Formatting
example/lib/main.dart, example/lib/screens/payment_sheet/payment_sheet_screen.dart
Formatting and minor widget-tree consolidation; no behavior changes.
Native Dependency Bumps
packages/stripe_android/android/build.gradle, packages/stripe_ios/ios/stripe_ios.podspec, packages/stripe_ios/ios/stripe_ios/Package.swift
Bumped Android stripe_version and Fresco (added middleware); updated iOS Stripe dependency from 25.0.1 → 25.6.0.
React Native Bridge Adjustments
packages/stripe_android/.../ReactContextBaseJavaModule.java, .../NativeStripeSdkModuleSpec.java, .../ReadableArray.java, .../ReadableMap.java
Added empty invalidate() stub in ReactContextBaseJavaModule and removed duplicate from NativeStripeSdkModuleSpec; simplified ReadableArray internals and added get(int); added isEmpty()/size() overrides in ReadableMap.
Android Event Emission
packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/EventEmitterCompat.kt
Added emitEmbeddedPaymentElementUpdateComplete(value: ReadableMap?) to emit a new event.
Android Async Image & UI Updates
packages/stripe_android/.../PaymentSheetManager.kt, .../customersheet/CustomerSheetManager.kt, .../pushprovisioning/AddToWalletButtonView.kt, .../NavigationBarView.kt, .../StripeAbstractComposeView.kt
Moved drawable→base64 conversion to coroutine suspend functions with timeout/error handling; converted functions to suspend where needed; integrated async image loading into payment option flows; re-enabled ripple drawable logic; updated NavigationBarView close button coloring and added lifecycle observer wiring for Compose view.
Android Card Funding Mapping & Opt-ins
packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/PaymentElementConfig.kt, packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/utils/Mappers.kt
Added mapToAllowedCardFundingTypes (OptIn to CardFundingFilteringPrivatePreview) mapping strings to PaymentSheet.CardFundingType; removed older ExperimentalCustomPaymentMethodsApi opt-in.
Android Connected Account Handling
packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/StripeSdkModule.kt
Resolve accountId from params.connectedAccountId or fallback to stripeAccountId; pass resolved accountId into FinancialConnectionsSheetManager in multiple flows.
iOS Financial Connections Account Override
packages/stripe_ios/.../StripeSdkImpl.swift
Temporarily override STPAPIClient.shared.stripeAccount with connectedAccountId during Financial Connections flows and restore original value after completion.
iOS Presentation Behavior
packages/stripe_ios/.../ConnectAccountOnboardingView.swift
Present empty modal first, attach React content view after presentation completes, and reveal host view post-animation; removed immediate didMoveToSuperview sync.
iOS Card Funding Support
packages/stripe_ios/.../StripeSdkImpl+Embedded.swift, .../StripeSdkImpl+PaymentSheet.swift
Imported CardFundingFilteringPrivatePreview; added computeAllowedCardFundingTypes(params:) and assign configuration.allowedCardFundingTypes when present.
Dart: Payment Method & Enums
packages/stripe_platform_interface/lib/src/models/ach_params.g.dart, .../payment_methods.dart, .../setup_intent.g.dart
Added PaymentMethodType.Link enum variant and updated generated JSON mappings.
Dart: Financial Connections Params
packages/stripe_platform_interface/lib/src/models/financial_connections.dart, .../financial_connections.freezed.dart, .../financial_connections.g.dart
Added optional connectedAccountId: String? to CollectFinancialConnectionsAccountsParams and propagated through generated freezed/equality/toJson code.
Dart: Card Funding Models & API
packages/stripe_platform_interface/lib/src/models/payment_sheet.dart, .../payment_sheet.freezed.dart, .../payment_sheet.g.dart
Introduced CardFundingType enum and CardFundingFiltering model; added cardFundingFiltering to SetupPaymentSheetParameters with full freezed and JSON serialization support (substantial generated changes).

Sequence Diagram(s)

mermaid
sequenceDiagram
participant Caller
participant StripeSdkModule
participant STPAPIClient
participant FinancialConnections
Note over Caller,FinancialConnections: Flow when connectedAccountId may be provided
Caller->>StripeSdkModule: presentFinancialConnections(params with connectedAccountId?)
StripeSdkModule->>STPAPIClient: save original stripeAccount
StripeSdkModule->>STPAPIClient: if connectedAccountId set -> set stripeAccount = connectedAccountId
StripeSdkModule->>FinancialConnections: present(..., wrappedResolve)
FinancialConnections-->>StripeSdkModule: on completion (success/failure)
StripeSdkModule->>STPAPIClient: restore original stripeAccount
StripeSdkModule-->>Caller: resolve wrappedResolve result

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇 I nibbled through diffs late at night,

Cards filtered clean, icons shining bright,
Connected accounts hopped into place,
Async bunnies raced the image-to-base64,
Cheers from a rabbit — tidy, spry, and light!

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title 'Sync' is too generic and vague, providing no meaningful information about the specific changes made across the multiple files and components in this pull request. Replace with a more descriptive title that captures the main changes, such as 'Upgrade Stripe SDKs to v25.6.0 and add card funding filtering support' or 'Update Stripe dependencies and add embedded payment element features'.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch sync

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/ConnectAccountOnboarding/ConnectAccountOnboardingView.swift (1)

47-74: ⚠️ Potential issue | 🟠 Major

Guard against stale completion revealing after a fast dismiss.

If visible flips to false while the presentation animation is in-flight, the completion still runs and can unhide the view or attach React content after dismissal. That yields a visible view when visible == false. Add a state guard (or token) before un-hiding.

✅ Suggested guard to prevent stale completion
 presenter.present(navigationController!, animated: true) { [weak self] in
-    guard let self = self else { return }
-    // Add React content after presentation animation finishes
-    self.viewController?.setReactContentView(self)
-    self.isHidden = false  // Show the view now that modal is presented
+    guard let self = self, self.visible, self.wasVisible else { return }
+    // Add React content after presentation animation finishes
+    self.viewController?.setReactContentView(self)
+    self.isHidden = false  // Show the view now that modal is presented
 }
packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/NavigationBarView.kt (1)

61-78: ⚠️ Potential issue | 🟠 Major

Add a contentDescription to the close button for accessibility.

Line 61 creates an icon-only button without an accessible label, which blocks screen-reader users. Add a localized content description.

🔧 Suggested fix
 ImageButton(context).apply {
+  contentDescription = context.getString(R.string.stripe_close_button)
   setImageDrawable(
     context.resources.getDrawable(
       android.R.drawable.ic_menu_close_clear_cancel,
       null,
     ),
   )
🤖 Fix all issues with AI agents
In
`@packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/PaymentSheetManager.kt`:
- Around line 585-628: Detekt flags the empty bodies in the Drawable.Callback
implementation inside waitForDrawableToLoad; either annotate the anonymous
object with a suppression (e.g., add `@Suppress`("Detekt.EmptyFunctionBlock") to
the callback object) or add explicit no-op bodies/comments to scheduleDrawable
and unscheduleDrawable (e.g., include a comment like // no-op or a trivial
return) so detekt no longer reports EmptyFunctionBlock while leaving
invalidateDrawable behavior unchanged.

In
`@packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/StripeSdkModule.kt`:
- Around line 1077-1088: The current assignment for accountId uses
getValOr(params, "connectedAccountId", null) which allows an empty string to
override stripeAccountId; change it to treat blank strings as absent by
normalizing the retrieved value to null when it's null or blank (e.g., call a
helper or inline check like treating the result of getValOr(...) as null if
isNullOrBlank()), then fall back to stripeAccountId; apply the same
normalization for the other occurrence around lines 1105-1116 so
FinancialConnectionsSheetManager receives either a real account id or the
fallback stripeAccountId, not an empty string.

In `@packages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe`
Sdk/StripeSdkImpl.swift:
- Around line 1140-1158: The code temporarily mutates the global
STPAPIClient.shared.stripeAccount in collectBankAccountToken and
collectFinancialConnectionsAccounts, causing race conditions; fix by serializing
the entire override->call->restore sequence on a dedicated serial DispatchQueue
(e.g., create a private static let stripeAccountQueue = DispatchQueue(label:
"com.app.stripeAccountQueue")) and perform the block that saves
originalStripeAccount, sets STPAPIClient.shared.stripeAccount, calls
FinancialConnections.presentForToken (or the corresponding FinancialConnections
call), and relies on the wrappedResolve to restore the originalStripeAccount all
on that queue so no two flows can interleave; apply the same queue-serialization
pattern to both collectBankAccountToken and collectFinancialConnectionsAccounts
(referencing STPAPIClient.shared.stripeAccount, wrappedResolve, and
FinancialConnections.presentForToken) or alternatively replace use of the global
by instantiating and using a per-instance STPAPIClient if supported.
🧹 Nitpick comments (3)
packages/stripe_android/android/src/main/kotlin/com/facebook/react/bridge/ReactContextBaseJavaModule.java (1)

44-47: Consider cleaning up formatting and adding documentation.

The lifecycle hook pattern is appropriate here, enabling StripeSdkModule to properly call super.invalidate() during cleanup. However, the formatting has unnecessary blank lines inside the method body.

✨ Suggested cleanup
-
-    public void invalidate() {
-
-    }
-
+    /**
+     * Called when the module is being invalidated. Subclasses should override
+     * to perform cleanup (e.g., releasing resources, clearing managers).
+     */
+    public void invalidate() {
+    }
packages/stripe_android/android/src/main/kotlin/com/facebook/react/bridge/ReadableArray.java (1)

47-53: Optional: toArrayList() could now leverage the populated ArrayList.

Since both constructors now populate the inherited ArrayList, this method could be simplified to avoid iterating over the JSONArray separately, ensuring consistency and reducing code duplication.

♻️ Suggested simplification
     `@NotNull`
     public ArrayList<Object> toArrayList() {
-        ArrayList<Object> arrayList = new ArrayList<>();
-        for (int i = 0; i < array.length(); i++) {
-            arrayList.add(array.opt(i));
-        }
-        return arrayList;
+        return new ArrayList<>(this);
     }
packages/stripe_ios/ios/stripe_ios/Package.swift (1)

15-15: Align SPM constraint with CocoaPods and repository patterns using a floating version range.

The CocoaPods constraint uses ~> 25.6.0 (floating patch range), while SPM is pinned to exact 25.6.0. Changing to .upToNextMinor(from: "25.6.0") maintains consistency across dependency managers and aligns with the repository's pattern of using floating ranges for Stripe dependencies.

♻️ Suggested change
-        .package(url: "https://github.com/stripe/stripe-ios-spm", exact: "25.6.0")
+        .package(url: "https://github.com/stripe/stripe-ios-spm", .upToNextMinor(from: "25.6.0"))

Comment on lines +1077 to 1088
// Use connectedAccountId from params if provided, otherwise fall back to global stripeAccountId
val accountId = getValOr(params, "connectedAccountId", null) ?: stripeAccountId

unregisterStripeUIManager(financialConnectionsSheetManager)
financialConnectionsSheetManager =
FinancialConnectionsSheetManager(
reactApplicationContext,
clientSecret,
FinancialConnectionsSheetManager.Mode.ForToken,
publishableKey,
stripeAccountId,
accountId,
).also {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Treat blank connectedAccountId as absent to avoid invalid overrides.

An empty string will currently bypass the fallback and be passed to the SDK. Consider normalizing blanks to null so it falls back to stripeAccountId.

💡 Suggested tweak
-    val accountId = getValOr(params, "connectedAccountId", null) ?: stripeAccountId
+    val accountId =
+      getValOr(params, "connectedAccountId", null)
+        ?.takeIf { it.isNotBlank() }
+        ?: stripeAccountId

Also applies to: 1105-1116

🤖 Prompt for AI Agents
In
`@packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/StripeSdkModule.kt`
around lines 1077 - 1088, The current assignment for accountId uses
getValOr(params, "connectedAccountId", null) which allows an empty string to
override stripeAccountId; change it to treat blank strings as absent by
normalizing the retrieved value to null when it's null or blank (e.g., call a
helper or inline check like treating the result of getValOr(...) as null if
isNullOrBlank()), then fall back to stripeAccountId; apply the same
normalization for the other occurrence around lines 1105-1116 so
FinancialConnectionsSheetManager receives either a real account id or the
fallback stripeAccountId, not an empty string.

…o delegate to JSONArray, matching the size() override

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 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/kotlin/com/facebook/react/bridge/ReadableArray.java`:
- Around line 51-59: The ReadableArray overrides size() and get(int) to proxy to
the underlying JSONArray but still uses JSONArray.opt() which returns null for
OOB (violating List.get() contract) and leaves ArrayList's iterator/toArray
using the empty backing store; update get(int) to throw
IndexOutOfBoundsException when index<0 or index>=array.length(), and implement
iterator() and both toArray() overloads in the ReadableArray class to iterate
over and materialize elements from the JSONArray (use array.get(index) or
equivalent safe access to build an Iterator<Object> and Object[]/T[] results) so
all List behaviors (iteration, toArray, get, size) consistently reflect the
JSONArray contents.

Comment on lines 51 to 59
@Override
public int size() {
return super.size();
return array.length();
}

@Override
public Object get(int index) {
return array.opt(index);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Correct bug-fix, but opt() silently swallows out-of-bounds and ArrayList.iterator() still bypasses these overrides.

The previous state was broken: super.size() always returned 0 (the ArrayList backing store is never populated), and super.get(i) always threw IndexOutOfBoundsException for the same reason. Both overrides fix that correctly.

Two residual concerns:

  1. opt() vs the List contractJSONArray.opt(index) returns null for out-of-bounds instead of throwing IndexOutOfBoundsException, which List.get() requires. Callers treating ReadableArray as a List<Object> could silently receive null for a bad index. getType() handles this gracefully (→ ReadableType.Null), but any external consumer relying on the exception for bounds checking would be misled.

  2. ArrayList.iterator() bypasses these overrides (pre-existing design gap, now more visible) — ArrayList's private Itr accesses elementData[] directly; it does not call get() or consult size(). As a result, for (Object o : readableArray), .iterator(), and .toArray() still iterate over the empty ArrayList backing store even though size() now returns the JSONArray length. Any code path that iterates via standard Java iteration will silently produce zero elements.

🔧 Optional: override iterator() and toArray() to stay consistent
+    `@Override`
+    `@NotNull`
+    public java.util.Iterator<Object> iterator() {
+        return toArrayList().iterator();
+    }
+
+    `@Override`
+    public Object[] toArray() {
+        return toArrayList().toArray();
+    }
🤖 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/facebook/react/bridge/ReadableArray.java`
around lines 51 - 59, The ReadableArray overrides size() and get(int) to proxy
to the underlying JSONArray but still uses JSONArray.opt() which returns null
for OOB (violating List.get() contract) and leaves ArrayList's iterator/toArray
using the empty backing store; update get(int) to throw
IndexOutOfBoundsException when index<0 or index>=array.length(), and implement
iterator() and both toArray() overloads in the ReadableArray class to iterate
over and materialize elements from the JSONArray (use array.get(index) or
equivalent safe access to build an Iterator<Object> and Object[]/T[] results) so
all List behaviors (iteration, toArray, get, size) consistently reflect the
JSONArray contents.

ReadableMap extends Dynamic which extends HashMap, but stores all data
in a private JSONObject field. The inherited HashMap.isEmpty() and
HashMap.size() always returned true/0 respectively, since no data is
ever put into the HashMap itself. This caused GooglePay config params
to be incorrectly treated as empty.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/stripe_android/android/src/main/kotlin/com/facebook/react/bridge/ReadableMap.java (1)

44-47: Remove the now-dead isEmpty() extension function in Extensions.kt.

In Kotlin, member functions always take precedence over extension functions with the same signature. Adding isEmpty() as a member here silently shadows the existing extension:

// Extensions.kt:67 — now unreachable for ReadableMap receivers
fun ReadableMap.isEmpty() = !this.keySetIterator().hasNextKey()

Any call site that previously resolved to the Kotlin extension will now silently dispatch to this Java member method. The two implementations are semantically equivalent, but the extension function is now dead code and should be deleted.

🧹 Proposed cleanup in Extensions.kt
-fun ReadableMap.isEmpty() = !this.keySetIterator().hasNextKey()
🤖 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/facebook/react/bridge/ReadableMap.java`
around lines 44 - 47, The Java class ReadableMap now defines a member method
isEmpty(), which takes precedence over the Kotlin extension fun
ReadableMap.isEmpty() in Extensions.kt and makes that extension dead; remove the
extension declaration fun ReadableMap.isEmpty() from Extensions.kt (or replace
its usage if intended) so there is no silently unreachable duplicate — keep the
Java ReadableMap.isEmpty() as the single implementation and delete the extension
function to clean up dead code.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@packages/stripe_android/android/src/main/kotlin/com/facebook/react/bridge/ReadableMap.java`:
- Around line 44-47: The Java class ReadableMap now defines a member method
isEmpty(), which takes precedence over the Kotlin extension fun
ReadableMap.isEmpty() in Extensions.kt and makes that extension dead; remove the
extension declaration fun ReadableMap.isEmpty() from Extensions.kt (or replace
its usage if intended) so there is no silently unreachable duplicate — keep the
Java ReadableMap.isEmpty() as the single implementation and delete the extension
function to clean up dead code.

@remonh87 remonh87 merged commit 8b3f4ab into main Feb 18, 2026
8 of 9 checks passed
@remonh87 remonh87 deleted the sync branch February 18, 2026 10:50
@coderabbitai coderabbitai Bot mentioned this pull request May 20, 2026
@coderabbitai coderabbitai Bot mentioned this pull request May 29, 2026
6 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants