Skip to content

Support payment messaging element#2438

Open
remonh87 wants to merge 7 commits into
mainfrom
support-payment-messaging-element
Open

Support payment messaging element#2438
remonh87 wants to merge 7 commits into
mainfrom
support-payment-messaging-element

Conversation

@remonh87

@remonh87 remonh87 commented Jun 27, 2026

Copy link
Copy Markdown
Member

Support the payment messaging element

Simulator Screenshot - iPhone 16e - 2026-06-27 at 14 27 33 Simulator Screenshot - iPhone 16e - 2026-06-27 at 14 27 30
  • Have you followed the guidelines in our Contributing document?
  • Have you checked to ensure there aren't other open Pull Requests for the same change?
  • Have you added an explanation of what your changes do and why you'd like us to include them?
  • Do all checks pass?
  • Have you developed the feature on the latest stable version of Flutter?

  • AI was used to generate or assist with generating this PR. Please specify below how you used AI to help you, and what steps you have taken to manually verify the changes.

Summary by CodeRabbit

  • New Features

    • Added Payment Method Messaging support, including a new widget, configuration models, and a sample screen.
    • Added platform support for iOS and Android with automatic sizing and height updates.
    • Added a new documentation page and sidebar link for setup, usage, and customization.
  • Bug Fixes

    • Improved layout and update handling so messaging refreshes correctly when configuration or appearance changes.
    • Updated native integration to better handle unsupported platforms and loading states.

realmeylisdev and others added 7 commits June 26, 2026 15:47
Freezed model for Stripe's PaymentMethodMessagingElement (Klarna,
Afterpay/Clearpay, Affirm). Includes optional locale to match the
Element API surface on both iOS and Android.
Platform-view widget that renders Stripe's promotional messaging
element for Klarna/Afterpay/Affirm. Ships on iOS and Android via
the PaymentMethodMessagingElement preview API; throws
UnsupportedError elsewhere. Native side reports height changes via
a per-viewId MethodChannel; didUpdateWidget forwards configuration
changes through updateConfiguration; dispose unregisters the handler.
- PaymentMethodMessagingElementView (under Stripe Sdk/) is a plain
  UIView that owns a Stripe SDK PaymentMethodMessagingElement, kicks
  off create(configuration:) on an @mainactor Task, embeds
  element.view with Auto Layout on success, and reports height
  changes via a callback. Guards against a missing publishable key
  and collapses to zero height on noContent / failed results.
- PaymentMethodMessagingElementFactory is a thin FlutterPlatformView
  adapter — per-viewId FlutterMethodChannel for updateConfiguration /
  onHeightChange, NSNumber-based amount decode for the Flutter codec.
- Register the factory in StripePlugin with viewId
  flutter.stripe/payment_method_messaging.
- Update Package.resolved to the 25.9.0 SPM revision that matches
  Package.swift's exact pin (main was committed with a stale resolve).

Uses the `@_spi(PaymentMethodMessagingElementPreview) @_spi(STP)
import StripePaymentSheet` import that the element is gated behind.
…m view

- Add the separate com.stripe:payment-method-messaging artifact
  dependency, pinned to the same stripe_version as stripe-android.
- StripePaymentMethodMessagingPlatformViewFactory mirrors the
  existing Aubecs/CardField factories, including the
  Map<String?, Any?>? creationParams type used across the repo.
- StripePaymentMethodMessagingPlatformView hosts an AndroidX
  ComposeView inside a FrameLayout, calls
  PaymentMethodMessagingElement.create(application) + configure(config)
  from a scope-tied coroutine, swaps composition state on result
  (Succeeded → render element.Content(appearance); NoContent/Failed
  → empty composition + collapsed height). Height changes are
  reported in dp via Modifier.onSizeChanged. dispose() cancels the
  scope, disposes composition, and clears the method-channel handler.
- Register the factory in StripeAndroidPlugin with viewId
  flutter.stripe/payment_method_messaging.

Uses Stripe's @OptIn(PaymentMethodMessagingElementPreview::class)
preview annotation.
payment-method-messaging:23.1.0 declares androidx.compose.foundation,
androidx.compose.ui, and androidx.compose.runtime at `runtime` scope
in its POM, so Kotlin compilation in this module can't see `Box`,
`Modifier`, `remember`, etc. without explicit `implementation`
dependencies. Pin the versions to 1.10.4 to match Stripe's own
declared dependency graph.
@remonh87 remonh87 requested a review from jonasbark June 27, 2026 15:16
@coderabbitai

coderabbitai Bot commented Jun 27, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a PaymentMethodMessaging Flutter widget that renders BNPL promotional messaging (Klarna, Afterpay/Clearpay, Affirm) via native iOS and Android platform views. The change includes Dart freezed models, platform-specific Swift/Kotlin implementations bridging Stripe's preview PaymentMethodMessagingElement, an example demo screen, and documentation.

Changes

PaymentMethodMessaging Feature

Layer / File(s) Summary
Dart data models and generated code
packages/stripe_platform_interface/lib/src/models/payment_method_messaging.dart, ...freezed.dart, ...g.dart, packages/stripe_platform_interface/lib/stripe_platform_interface.dart
Defines PaymentMethodMessagingConfiguration, PaymentMethodMessagingAppearance, PaymentMethodMessagingStyle, PaymentMethodMessagingFont, and PaymentMethodMessagingColor with freezed/JSON-serializable codegen and public re-exports.
Flutter PaymentMethodMessaging widget
packages/stripe/lib/src/widgets/payment_method_messaging.dart, packages/stripe/lib/flutter_stripe.dart
StatefulWidget wrapping the native platform view with per-view MethodChannel, height-change callback, didUpdateWidget configuration push, and platform-specific build paths (UiKitView/PlatformViewLink).
Android config parsing and Compose view
packages/stripe_android/android/build.gradle, packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/PaymentMethodMesagingElementConfig.kt, ...PaymentMethodMessagingElementView.kt, ...PaymentMethodMessagingElementViewManager.kt, com/facebook/react/viewmanagers/...
Adds Gradle dependency on payment-method-messaging, configuration/appearance parsers, a Jetpack Compose view with event-channel-driven configure/appearance handling and height measurement, and the RN view manager with codegen delegate/interface.
Android Flutter platform view factory and plugin wiring
packages/stripe_android/android/src/main/kotlin/com/flutter/stripe/StripePaymentMethodMessagingPlatformView.kt, ...Factory.kt, StripeAndroidPlugin.kt
StripePaymentMethodMessagingPlatformView bridges Flutter method calls to the RN view manager; StripePaymentMethodMessagingPlatformViewFactory creates per-view channels; StripeAndroidPlugin registers the factory under flutter.stripe/payment_method_messaging.
iOS Swift config parsing, container view, and Flutter factory
packages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/PaymentMethodMessagingElementConfig.swift, ...ElementView.swift, PaymentMethodMessagingElementFactory.swift, StripePlugin.swift
Appearance/configuration builders, a UIView container with async Stripe element lifecycle (attach/detach, height events, configure results), a FlutterPlatformViewFactory with per-view FlutterMethodChannel, and registration in StripePlugin.
Example app screen and navigation
example/lib/screens/others/payment_method_messaging_screen.dart, example/lib/screens/screens.dart
Adds PaymentMethodMessagingScreen with slider/chip controls for amount, market, style, and payment methods, and registers it under the "Others" section limited to iOS/Android.
iOS Xcode project and dependency updates
example/ios/Runner.xcodeproj/project.pbxproj, example/ios/Runner.xcodeproj/...swiftpm/Package.resolved, example/ios/Runner.xcworkspace/...swiftpm/Package.resolved
Wires FlutterGeneratedPluginSwiftPackage, removes CocoaPods embed script phase, and bumps stripe-ios-spm to 25.17.0.
Documentation
docs/payment_method_messaging.mdx, docs.json
New MDX page covering widget setup, configuration parameters, appearance customization, onHeightChange, platform support, and related links; sidebar entry added.

Sequence Diagram

sequenceDiagram
  participant App as Flutter App
  participant Widget as PaymentMethodMessaging
  participant Channel as FlutterMethodChannel
  participant Native as iOS/Android Native View

  App->>Widget: build(configuration, appearance)
  Widget->>Native: create platform view with creationParams
  Native->>Channel: paymentMethodMessagingElementConfigureResult(loading)
  Native->>Channel: paymentMethodMessagingElementDidUpdateHeight(height)
  Channel->>Widget: setState(_height) + onHeightChange(height)
  App->>Widget: setState(new configuration)
  Widget->>Channel: invokeMethod(updateConfiguration, params)
  Channel->>Native: apply new configuration/appearance
  Native->>Channel: paymentMethodMessagingElementDidUpdateHeight(newHeight)
  Channel->>Widget: setState(_height) + onHeightChange(newHeight)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Possibly related PRs

  • flutter-stripe/flutter_stripe#2379: Shares the same native event emitter method names (emitPaymentMethodMessagingElementDidUpdateHeight, configure result events) and overall bridge architecture for PaymentMethodMessaging.

Suggested reviewers

  • jonasbark

🐇 A new widget hops into the tree,
With Klarna and Affirm dancing free!
From Swift to Kotlin, channels ring,
Height updates on every spring 🌸
Now BNPL messaging takes wing! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.92% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding support for Stripe’s Payment Method Messaging element.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch support-payment-messaging-element

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.

@remonh87

Copy link
Copy Markdown
Member Author

Based on #2403

@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: 10

Caution

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

⚠️ Outside diff range comments (1)
example/ios/Runner.xcodeproj/project.pbxproj (1)

198-205: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Restore CocoaPods embedding for Runner

example/ios/Runner.xcodeproj/project.pbxproj still links Pods_Runner.framework, and example/ios/Podfile uses use_frameworks!; without [CP] Embed Pods Frameworks, CocoaPods frameworks won’t be bundled into the app and launch/archive can fail.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@example/ios/Runner.xcodeproj/project.pbxproj` around lines 198 - 205, The
Runner Xcode project is missing the CocoaPods framework embedding step, so the
Pods_Runner framework will be linked but not bundled. Update the Runner target’s
buildPhases in project.pbxproj to include the CocoaPods “[CP] Embed Pods
Frameworks” phase alongside the existing Pods-related phases, and ensure it is
wired correctly for the Runner target that already uses use_frameworks! in
Podfile.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@example/lib/screens/others/payment_method_messaging_screen.dart`:
- Around line 165-176: The payment method chip selection logic in the messaging
screen allows `_paymentMethods` to become empty, which conflicts with the
example state. Update the `FilterChip` `onSelected` handler in the
`PaymentMethodMessaging` screen so the last selected method cannot be removed,
or explicitly treat an empty list as “use dashboard defaults” in the UI
text/state. Use the `PaymentMethodMessagingPaymentMethod.values` loop and
`_paymentMethods` state to locate the change.

In `@packages/stripe_android/android/build.gradle`:
- Around line 77-82: The Compose dependencies added for
StripePaymentMethodMessagingPlatformView are redundantly pinned and override
BOM-managed alignment. Update the android/build.gradle declarations in the
Stripe Android module to keep only the Compose BOM-backed dependencies and
remove the explicit versioned coordinates for androidx.compose.ui:ui,
androidx.compose.foundation:foundation, and androidx.compose.runtime, so the
module stays on a consistent Compose set.

In
`@packages/stripe_android/android/src/main/kotlin/com/flutter/stripe/StripePaymentMethodMessagingPlatformView.kt`:
- Around line 31-38: Event routing in StripePaymentMethodMessagingPlatformView
is shared across instances because the callback registration uses the constant
paymentMethodMessagingElement prefix instead of the PlatformView id. Update the
view’s native event wiring in constructure/create and onMethodCall handling to
key registrations, emissions, and filtering by the provided id so each
PaymentMethodMessaging instance only receives its own height/configure events.
Ensure dispose unregisters only that viewId-specific route and does not affect
other active instances.

In
`@packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/PaymentMethodMesagingElementConfig.kt`:
- Around line 57-58: The themed color lookup in
PaymentMethodMesagingElementConfig currently keys off the provided style/theme,
so the dark branch is skipped for { light, dark } colors unless DARK is
explicitly forced. Update the color resolution logic used by
dynamicColorFromParams and the related theme checks to read from the device UI
mode instead of the style parameter, so light/dark variants follow the system
appearance consistently even when light or flat styling is selected.

In
`@packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/PaymentMethodMessagingElementViewManager.kt`:
- Around line 39-49: The setAppearance prop handler in
PaymentMethodMessagingElementViewManager currently exits early when the value is
null, which leaves the previous native styling applied; update setAppearance so
that clearing the prop explicitly resets PaymentMethodMessagingElementView back
to its default appearance instead of returning unchanged. Use the existing
parseAppearance and view.appearance flow for the non-null case, and add the null
branch to restore defaults on the view instance.

In
`@packages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/PaymentMethodMessagingElementFactory.swift`:
- Around line 82-89: updateProps in PaymentMethodMessagingElementFactory is not
resetting containerView.appearance when Dart sends null or omits the field, so
the previous appearance sticks around; change the property update logic so it
explicitly clears or reverts appearance to the SDK default when
arguments["appearance"] is missing or nil, while still applying an NSDictionary
when provided, and keep the same behavior for configuration.
- Around line 63-69: The fixed paymentMethodMessagingElement prefix used in
PaymentMethodMessagingElementFactory is not safe for multiple active views
because StripePlugin.registerChannel(_:forPrefix:) only retains one channel per
prefix. Update the registration flow to use a unique per-instance prefix or
another instance-scoped channel identifier in
PaymentMethodMessagingElementPlatformView/PaymentMethodMessagingElementFactory,
and ensure deinit only unregisters that instance’s own channel so one widget
cannot steal or clear callbacks for another.

In `@packages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe`
Sdk/PaymentMethodMessagingElementView.swift:
- Around line 27-45: The MessagingElement update flow in
PaymentMethodMessagingElementView can initialize twice and let an older async
create() completion overwrite newer state. Coalesce appearance and configuration
changes so initMessagingElement(config:) runs once per logical update, or add a
version/token check around createMessagingElement completion to ignore stale
results. Use the appearance, configuration, lastConfig, and initMessagingElement
symbols to keep only the latest element and propagate height/status from the
newest request.

In `@packages/stripe/lib/src/widgets/payment_method_messaging.dart`:
- Around line 116-121: The PlatformViewsService.initSurfaceAndroidView call in
payment_method_messaging.dart is hardcoding left-to-right direction, which
breaks right-to-left layouts. Update the widget code that builds the platform
view to use the ambient direction from context instead of TextDirection.ltr,
specifically by reading the current Directionality in the same build path and
passing that value into the initSurfaceAndroidView call.
- Around line 96-130: Guard the native platform-view selection in
payment_method_messaging.dart by checking kIsWeb before the
defaultTargetPlatform iOS/android branches, since defaultTargetPlatform can
still report native values in browsers. Update the logic around the platform
widget construction so UiKitView and PlatformViewLink are never built on web,
and provide a safe web fallback or throw a web-specific unsupported error from
the same widget-building code.

---

Outside diff comments:
In `@example/ios/Runner.xcodeproj/project.pbxproj`:
- Around line 198-205: The Runner Xcode project is missing the CocoaPods
framework embedding step, so the Pods_Runner framework will be linked but not
bundled. Update the Runner target’s buildPhases in project.pbxproj to include
the CocoaPods “[CP] Embed Pods Frameworks” phase alongside the existing
Pods-related phases, and ensure it is wired correctly for the Runner target that
already uses use_frameworks! in Podfile.
🪄 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: 9d919289-13d8-4d4d-9183-c6324c173f1f

📥 Commits

Reviewing files that changed from the base of the PR and between 7f5463c and cf6823b.

📒 Files selected for processing (26)
  • docs.json
  • docs/payment_method_messaging.mdx
  • example/ios/Runner.xcodeproj/project.pbxproj
  • example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
  • example/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved
  • example/lib/screens/others/payment_method_messaging_screen.dart
  • example/lib/screens/screens.dart
  • packages/stripe/lib/flutter_stripe.dart
  • packages/stripe/lib/src/widgets/payment_method_messaging.dart
  • packages/stripe_android/android/build.gradle
  • packages/stripe_android/android/src/main/kotlin/com/facebook/react/viewmanagers/PaymentMethodMessagingElementViewManagerDelegate.java
  • packages/stripe_android/android/src/main/kotlin/com/facebook/react/viewmanagers/PaymentMethodMessagingElementViewManagerInterface.java
  • packages/stripe_android/android/src/main/kotlin/com/flutter/stripe/StripeAndroidPlugin.kt
  • packages/stripe_android/android/src/main/kotlin/com/flutter/stripe/StripePaymentMethodMessagingPlatformView.kt
  • packages/stripe_android/android/src/main/kotlin/com/flutter/stripe/StripePaymentMethodMessagingPlatformViewFactory.kt
  • packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/PaymentMethodMesagingElementConfig.kt
  • packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/PaymentMethodMessagingElementView.kt
  • packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/PaymentMethodMessagingElementViewManager.kt
  • packages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/PaymentMethodMessagingElementFactory.swift
  • packages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/PaymentMethodMessagingElementConfig.swift
  • packages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/PaymentMethodMessagingElementView.swift
  • packages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/StripePlugin.swift
  • packages/stripe_platform_interface/lib/src/models/payment_method_messaging.dart
  • packages/stripe_platform_interface/lib/src/models/payment_method_messaging.freezed.dart
  • packages/stripe_platform_interface/lib/src/models/payment_method_messaging.g.dart
  • packages/stripe_platform_interface/lib/stripe_platform_interface.dart

Comment on lines +165 to +176
for (final method in PaymentMethodMessagingPaymentMethod.values)
FilterChip(
label: Text(method.value),
selected: _paymentMethods.contains(method),
onSelected: (selected) => setState(() {
if (selected) {
_paymentMethods.add(method);
} else {
_paymentMethods.remove(method);
}
_isLoading = true;
}),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Keep at least one payment method selected.

An empty paymentMethods list falls back to dashboard defaults, so deselecting every chip makes the example UI say “none selected” while the element can still render messaging. Either prevent removing the last chip or label that state explicitly as “use dashboard defaults”.

Suggested fix
-                onSelected: (selected) => setState(() {
-                  if (selected) {
-                    _paymentMethods.add(method);
-                  } else {
-                    _paymentMethods.remove(method);
-                  }
-                  _isLoading = true;
-                }),
+                onSelected: (selected) {
+                  if (!selected && _paymentMethods.length == 1) {
+                    return;
+                  }
+                  setState(() {
+                    if (selected) {
+                      _paymentMethods.add(method);
+                    } else {
+                      _paymentMethods.remove(method);
+                    }
+                    _isLoading = 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.

Suggested change
for (final method in PaymentMethodMessagingPaymentMethod.values)
FilterChip(
label: Text(method.value),
selected: _paymentMethods.contains(method),
onSelected: (selected) => setState(() {
if (selected) {
_paymentMethods.add(method);
} else {
_paymentMethods.remove(method);
}
_isLoading = true;
}),
for (final method in PaymentMethodMessagingPaymentMethod.values)
FilterChip(
label: Text(method.value),
selected: _paymentMethods.contains(method),
onSelected: (selected) {
if (!selected && _paymentMethods.length == 1) {
return;
}
setState(() {
if (selected) {
_paymentMethods.add(method);
} else {
_paymentMethods.remove(method);
}
_isLoading = true;
});
},
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@example/lib/screens/others/payment_method_messaging_screen.dart` around lines
165 - 176, The payment method chip selection logic in the messaging screen
allows `_paymentMethods` to become empty, which conflicts with the example
state. Update the `FilterChip` `onSelected` handler in the
`PaymentMethodMessaging` screen so the last selected method cannot be removed,
or explicitly treat an empty list as “use dashboard defaults” in the UI
text/state. Use the `PaymentMethodMessagingPaymentMethod.values` loop and
`_paymentMethods` state to locate the change.

Comment on lines +77 to +82
// Compose modules used by StripePaymentMethodMessagingPlatformView. The
// payment-method-messaging artifact declares these at runtime scope only,
// so consuming modules must add them to the compile classpath explicitly.
implementation "androidx.compose.ui:ui:1.10.4"
implementation "androidx.compose.foundation:foundation:1.10.4"
implementation "androidx.compose.runtime:runtime:1.10.4"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== build.gradle snippet =="
sed -n '68,100p' packages/stripe_android/android/build.gradle

echo
echo "== Compose/BOM/compiler settings across the repo =="
rg -n \
  -g '*.gradle' -g '*.gradle.kts' \
  'compose-bom|androidx\.compose\.ui:ui|androidx\.compose\.foundation:foundation|androidx\.compose\.runtime:runtime|kotlinCompilerExtensionVersion|composeOptions|buildFeatures' \
  .

Repository: flutter-stripe/flutter_stripe

Length of output: 3153


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== stripe_android/android/build.gradle =="
sed -n '1,130p' packages/stripe_android/android/build.gradle

echo
echo "== any other Compose version declarations =="
rg -n -g '*.gradle' -g '*.gradle.kts' \
  'androidx\.compose:compose-bom|androidx\.compose\.ui:ui:|androidx\.compose\.foundation:foundation:|androidx\.compose\.runtime:runtime:|kotlinCompilerExtensionVersion|composeOptions' \
  .

Repository: flutter-stripe/flutter_stripe

Length of output: 4185


Remove the versioned Compose dependencies here.
packages/stripe_android/android/build.gradle:80-93 already brings in the same artifacts through the Compose BOM, so the explicit 1.10.4 coordinates override BOM-managed alignment and leave this module on a mixed Compose set. Keep only the BOM-backed declarations.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/stripe_android/android/build.gradle` around lines 77 - 82, The
Compose dependencies added for StripePaymentMethodMessagingPlatformView are
redundantly pinned and override BOM-managed alignment. Update the
android/build.gradle declarations in the Stripe Android module to keep only the
Compose BOM-backed dependencies and remove the explicit versioned coordinates
for androidx.compose.ui:ui, androidx.compose.foundation:foundation, and
androidx.compose.runtime, so the module stays on a consistent Compose set.

Comment on lines +31 to +38
class StripePaymentMethodMessagingPlatformView(
context: Context,
private val channel: MethodChannel,
id: Int,
creationParams: Map<String?, Any?>?,
private val viewManager: PaymentMethodMessagingElementViewManager,
sdkAccessor: () -> StripeSdkModule,
) : PlatformView, MethodChannel.MethodCallHandler {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Event routing isn't isolated per PlatformView instance.

Each widget has its own MethodChannel, but this wrapper registers native callbacks against the constant paymentMethodMessagingElement prefix and never uses id. Two PaymentMethodMessaging views on screen cannot disambiguate height/configure events: either the newest view steals them or both views observe the same callbacks. Please key the native event registration/emission by viewId (or include viewId in the payload and filter before forwarding) and only unregister that instance’s route on dispose.

Also applies to: 45-49, 68-71

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/stripe_android/android/src/main/kotlin/com/flutter/stripe/StripePaymentMethodMessagingPlatformView.kt`
around lines 31 - 38, Event routing in StripePaymentMethodMessagingPlatformView
is shared across instances because the callback registration uses the constant
paymentMethodMessagingElement prefix instead of the PlatformView id. Update the
view’s native event wiring in constructure/create and onMethodCall handling to
key registrations, emissions, and filtering by the provided id so each
PaymentMethodMessaging instance only receives its own height/configure events.
Ensure dispose unregisters only that viewId-specific route and does not affect
other active instances.

Comment on lines +57 to +58
val textColor = dynamicColorFromParams(map, "textColor", theme)
val linkTextColor = dynamicColorFromParams(map, "linkTextColor", theme)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use UI mode for themed colors instead of style.

Line 119 keys off theme == DARK, so { light, dark } colors never use their dark branch unless the caller explicitly forces the dark theme. On devices running dark mode with light or flat styling, this renders the wrong color.

Proposed fix
-  val textColor = dynamicColorFromParams(map, "textColor", theme)
-  val linkTextColor = dynamicColorFromParams(map, "linkTextColor", theme)
+  val textColor = dynamicColorFromParams(map, "textColor", context)
+  val linkTextColor = dynamicColorFromParams(map, "linkTextColor", context)
@@
 private fun dynamicColorFromParams(
   params: ReadableMap?,
   key: String,
-  theme: PaymentMethodMessagingElement.Appearance.Theme,
+  context: Context,
 ): Int? {
@@
-    val isDark = theme == PaymentMethodMessagingElement.Appearance.Theme.DARK
+    val isDark =
+      (context.resources.configuration.uiMode and android.content.res.Configuration.UI_MODE_NIGHT_MASK) ==
+        android.content.res.Configuration.UI_MODE_NIGHT_YES

Also applies to: 107-129

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/PaymentMethodMesagingElementConfig.kt`
around lines 57 - 58, The themed color lookup in
PaymentMethodMesagingElementConfig currently keys off the provided style/theme,
so the dark branch is skipped for { light, dark } colors unless DARK is
explicitly forced. Update the color resolution logic used by
dynamicColorFromParams and the related theme checks to read from the device UI
mode instead of the style parameter, so light/dark variants follow the system
appearance consistently even when light or flat styling is selected.

Comment on lines +39 to +49
@ReactProp(name = "appearance")
override fun setAppearance(
view: PaymentMethodMessagingElementView?,
value: Dynamic?,
) {
val readableMap = value?.asMapOrNull() ?: return
view?.let {
val appearance = parseAppearance(readableMap, view.context)
view.appearance(appearance)
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the relevant files and symbols
git ls-files | rg 'PaymentMethodMessagingElementViewManager\.kt|PaymentMethodMessagingElementView\.kt|Appearance|parseAppearance'

# Show an outline of the manager file
ast-grep outline packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/PaymentMethodMessagingElementViewManager.kt --view expanded

# Find the appearance-related methods and fields in the view implementation
if [ -f packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/PaymentMethodMessagingElementView.kt ]; then
  ast-grep outline packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/PaymentMethodMessagingElementView.kt --view expanded
fi

# Search for appearance handling and prop defaults
rg -n "fun setAppearance|appearance\(|parseAppearance|Appearance\(" packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk

Repository: flutter-stripe/flutter_stripe

Length of output: 6164


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,240p' packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/PaymentMethodMessagingElementView.kt

printf '\n--- parse helper ---\n'
sed -n '1,220p' packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/PaymentMethodMesagingElementConfig.kt

printf '\n--- address sheet comparison ---\n'
sed -n '1,120p' packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/addresssheet/AddressSheetViewManager.kt
sed -n '1,170p' packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/addresssheet/AddressSheetView.kt

Repository: flutter-stripe/flutter_stripe

Length of output: 19891


Reset the native appearance when the prop is cleared.
setAppearance returns on null, so removing or clearing the optional prop leaves the last custom styling in place instead of reverting to defaults.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/PaymentMethodMessagingElementViewManager.kt`
around lines 39 - 49, The setAppearance prop handler in
PaymentMethodMessagingElementViewManager currently exits early when the value is
null, which leaves the previous native styling applied; update setAppearance so
that clearing the prop explicitly resets PaymentMethodMessagingElementView back
to its default appearance instead of returning unchanged. Use the existing
parseAppearance and view.appearance flow for the non-null case, and add the null
branch to restore defaults on the view instance.

Comment on lines +63 to +69
StripePlugin.registerChannel(channel, forPrefix: PaymentMethodMessagingElementPlatformView.eventPrefix)
updateProps(args)
}

deinit {
channel.setMethodCallHandler(nil)
StripePlugin.unregisterChannel(forPrefix: PaymentMethodMessagingElementPlatformView.eventPrefix)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Per-prefix channel registration is not multi-view safe.

StripePlugin.registerChannel(_:forPrefix:) keeps only one channel for each prefix. Registering every instance under the fixed paymentMethodMessagingElement prefix means the newest view steals all callbacks, and deinit from any instance removes the override for the others. Two widgets on screen—or a replace-before-deinit rebuild—will drop height/configuration events.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/PaymentMethodMessagingElementFactory.swift`
around lines 63 - 69, The fixed paymentMethodMessagingElement prefix used in
PaymentMethodMessagingElementFactory is not safe for multiple active views
because StripePlugin.registerChannel(_:forPrefix:) only retains one channel per
prefix. Update the registration flow to use a unique per-instance prefix or
another instance-scoped channel identifier in
PaymentMethodMessagingElementPlatformView/PaymentMethodMessagingElementFactory,
and ensure deinit only unregisters that instance’s own channel so one widget
cannot steal or clear callbacks for another.

Comment on lines +82 to +89
private func updateProps(_ args: Any?) {
guard let arguments = args as? [String: Any] else { return }
if let appearance = arguments["appearance"] as? NSDictionary {
containerView.appearance = appearance
}
if let configuration = arguments["configuration"] as? NSDictionary {
containerView.configuration = configuration
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

appearance = null cannot restore the SDK defaults.

updateProps only forwards appearance when it is an NSDictionary. If Dart later omits that field or sends null, the container keeps the previous appearance, so the widget cannot revert to the default native styling.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/PaymentMethodMessagingElementFactory.swift`
around lines 82 - 89, updateProps in PaymentMethodMessagingElementFactory is not
resetting containerView.appearance when Dart sends null or omits the field, so
the previous appearance sticks around; change the property update logic so it
explicitly clears or reverts appearance to the SDK default when
arguments["appearance"] is missing or nil, while still applying an NSDictionary
when provided, and keep the same behavior for configuration.

Comment on lines +27 to +45
@objc var appearance: NSDictionary? {
didSet {
if let appearance = appearance {
appearanceConfig = PaymentMethodMessagingElementConfig.buildAppearanceFromParams(params: appearance)
// Re-initialize if configuration already exists
if let config = lastConfig {
initMessagingElement(config: config)
}
}
}
}

@objc var configuration: NSDictionary? {
didSet {
if let configuration = configuration {
lastConfig = configuration
initMessagingElement(config: configuration)
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Coalesce prop updates or discard stale create() completions.

A combined update can initialize twice here: appearance.didSet reuses lastConfig, then configuration.didSet starts a second async create(). Since those tasks are not versioned or cancelled, the older request can finish last and replace the newer element or emit stale height/status back to Dart.

Also applies to: 114-156

🧰 Tools
🪛 GitHub Check: Typo CI

[warning] 27-27: objc
"objc" is a typo. Did you mean "obj"?


[warning] 39-39: objc
"objc" is a typo. Did you mean "obj"?

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe`
Sdk/PaymentMethodMessagingElementView.swift around lines 27 - 45, The
MessagingElement update flow in PaymentMethodMessagingElementView can initialize
twice and let an older async create() completion overwrite newer state. Coalesce
appearance and configuration changes so initMessagingElement(config:) runs once
per logical update, or add a version/token check around createMessagingElement
completion to ignore stale results. Use the appearance, configuration,
lastConfig, and initMessagingElement symbols to keep only the latest element and
propagate height/status from the newest request.

Comment on lines +96 to +130
if (defaultTargetPlatform == TargetPlatform.iOS) {
platform = UiKitView(
viewType: _viewType,
creationParamsCodec: const StandardMessageCodec(),
creationParams: creationParams,
onPlatformViewCreated: onPlatformViewCreated,
);
} else if (defaultTargetPlatform == TargetPlatform.android) {
platform = PlatformViewLink(
viewType: _viewType,
surfaceFactory: (context, controller) {
return AndroidViewSurface(
controller: controller as AndroidViewController,
hitTestBehavior: PlatformViewHitTestBehavior.opaque,
gestureRecognizers:
const <Factory<OneSequenceGestureRecognizer>>{},
);
},
onCreatePlatformView: (params) {
onPlatformViewCreated(params.id);
return PlatformViewsService.initSurfaceAndroidView(
id: params.id,
viewType: _viewType,
layoutDirection: TextDirection.ltr,
creationParams: creationParams,
creationParamsCodec: const StandardMessageCodec(),
)
..addOnPlatformViewCreatedListener(
params.onPlatformViewCreated,
)
..create();
},
);
} else {
throw UnsupportedError('Unsupported platform view');

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Are UiKitViewandPlatformViewLinksupported on Flutter Web, and candefaultTargetPlatform still resolve to iOS/Android on web?

💡 Result:

UiKitView and PlatformViewLink are not supported on Flutter Web [1][2][3]. UiKitView is specifically designed for embedding native iOS views [1], and PlatformViewLink is a low-level widget used to integrate platform views (like AndroidView or UiKitView) with the Flutter framework's compositing and gesture systems [4][5]. On Flutter Web, the equivalent widget for embedding native browser elements is HtmlElementView [2][6]. Regarding defaultTargetPlatform, it can resolve to TargetPlatform.iOS or TargetPlatform.android when running on the web [7][8]. The Flutter framework determines this value by inspecting the browser's environment [7]. In practice, defaultTargetPlatform on the web is intended to reflect the underlying operating system of the browser environment [7][9]. While it defaulted to TargetPlatform.android in earlier versions [10][11], it now correctly resolves to iOS, macOS, android, windows, or linux based on the detected operating system [7].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the widget file and surrounding context
sed -n '1,220p' packages/stripe/lib/src/widgets/payment_method_messaging.dart

echo
echo "--- pubspec/package context ---"
sed -n '1,220p' packages/stripe/pubspec.yaml

echo
echo "--- search for web/platform guards in this package ---"
rg -n "kIsWeb|UnsupportedError\\(|defaultTargetPlatform|UiKitView|PlatformViewLink|HtmlElementView" packages/stripe/lib/src -S

Repository: flutter-stripe/flutter_stripe

Length of output: 10011


Guard the native platform-view branches on web. defaultTargetPlatform can still resolve to iOS/android in browsers, so this can build UiKitView or PlatformViewLink where platform views are unsupported. Add an explicit kIsWeb check (or a web fallback) before these branches.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/stripe/lib/src/widgets/payment_method_messaging.dart` around lines
96 - 130, Guard the native platform-view selection in
payment_method_messaging.dart by checking kIsWeb before the
defaultTargetPlatform iOS/android branches, since defaultTargetPlatform can
still report native values in browsers. Update the logic around the platform
widget construction so UiKitView and PlatformViewLink are never built on web,
and provide a safe web fallback or throw a web-specific unsupported error from
the same widget-building code.

Comment on lines +116 to +121
return PlatformViewsService.initSurfaceAndroidView(
id: params.id,
viewType: _viewType,
layoutDirection: TextDirection.ltr,
creationParams: creationParams,
creationParamsCodec: const StandardMessageCodec(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

For Flutter Android platform views created with PlatformViewsService.initSurfaceAndroidView, should layoutDirectioncome fromDirectionality.of(context) to support RTL layouts?

💡 Result:

Yes, you should use Directionality.of(context) (or Directionality.maybeOf(context) if the ambient directionality might be missing) for the layoutDirection parameter when creating a Flutter Android platform view via PlatformViewsService.initSurfaceAndroidView [1][2][3]. Providing the correct layout direction allows the native Android view to respect the app's ambient RTL or LTR layout settings [4]. If this value is not provided correctly, the native view may not handle layout-dependent features—such as text alignment, padding, or coordinate systems—in alignment with the rest of your Flutter interface [5][6][7]. While some documentation examples may use a hardcoded TextDirection.ltr for simplicity [8], relying on Directionality.of(context) is the recommended practice for ensuring your platform views behave consistently with the surrounding Flutter widgets in applications that support multiple languages or locales [1][2][3]. Top results: [1][2][3][4]

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the target file around the cited lines.
sed -n '1,220p' packages/stripe/lib/src/widgets/payment_method_messaging.dart

Repository: flutter-stripe/flutter_stripe

Length of output: 5013


Use the ambient text direction here. Hardcoding TextDirection.ltr breaks RTL apps; pass Directionality.maybeOf(context) ?? TextDirection.ltr to PlatformViewsService.initSurfaceAndroidView.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/stripe/lib/src/widgets/payment_method_messaging.dart` around lines
116 - 121, The PlatformViewsService.initSurfaceAndroidView call in
payment_method_messaging.dart is hardcoding left-to-right direction, which
breaks right-to-left layouts. Update the widget code that builds the platform
view to use the ambient direction from context instead of TextDirection.ltr,
specifically by reading the current Directionality in the same build path and
passing that value into the initSurfaceAndroidView call.

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