Sync v0.66.0#2421
Conversation
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds Multibanco docs and examples, introduces confirm-token handler APIs and widget lifecycle wiring, implements embedded payment element platform plumbing, and adds Checkout TurboModule/session mapping and events across Android and iOS. Platform interface models and generated serializers now support Multibanco instead of UPI. ChangesCross-platform payments and embedded element updates
Estimated code review effort Possibly related PRs
Suggested labels Suggested reviewers
Poem
✨ Finishing Touches🧪 Generate unit tests (beta)
|
There was a problem hiding this comment.
Actionable comments posted: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (5)
packages/stripe_android/android/src/main/kotlin/com/flutter/stripe/StripeSdkEmbeddedPaymentElementPlatformView.kt (2)
31-44:⚠️ Potential issue | 🟠 Major | 🏗️ Heavy liftScope RN event prefix to per-view id to prevent cross-instance event routing.
Android
StripeSdkEmbeddedPaymentElementPlatformViewregisters/unregisters React Native’sDeviceEventManagerModule.RCTDeviceEventEmitterusing the fixed prefix"embeddedPaymentElement", which is shared across all platform view instances. Even though the embedded element view type/channel is per-viewIdon the Flutter side (flutter.stripe/embedded_payment_element/$viewId), the native event emitter prefix isn’t scoped, so multiple instances can still overwrite routing and get torn down together—scope the prefix withviewId(and align the JS/Dart listener to that same per-id event name).🤖 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/StripeSdkEmbeddedPaymentElementPlatformView.kt` around lines 31 - 44, The RN event prefix is global and must be scoped per view instance: update StripeSdkEmbeddedPaymentElementPlatformView to register/unregister the DeviceEventManagerModule.RCTDeviceEventEmitter with a per-view prefix that includes the view's id (e.g. "embeddedPaymentElement_$viewId") instead of the fixed "embeddedPaymentElement"; change the registration call (where registerChannelForPrefix is invoked) and the corresponding unregister call in dispose() to use the same formatted prefix built from the instance's viewId (use the existing viewId/embeddedView identifiers in the class), and ensure the JS/Dart listener is updated to subscribe to the matching per-id event name (flutter.stripe/embedded_payment_element/$viewId) so events are routed and torn down per-instance.
47-61:⚠️ Potential issue | 🟠 MajorFix/clarify how
embeddedPaymentElement.confirmdelivers the confirmation outcome to DartOn Android,
confirmnow returns immediately withresult.success(null), but the actual outcome is emitted to Dart via the event/method callembeddedPaymentElementFormSheetConfirmCompletewith a payload like{ status: "completed"|"canceled"|"failed", error?: ... }, which Dart forwards toonFormSheetConfirmComplete.However,
EmbeddedPaymentElementController.confirm()awaitsinvokeMethod('confirm'), so on Android it resolves tonull(unlike iOS, where the MethodChannelconfirmreturns the payload map and also invokesonConfirmComplete). Callers usingcontroller.confirm()will lose the confirmation result on Android—either return the same payload from the AndroidconfirmMethodChannel (wait for the completion event) or document that Android results are only delivered viaonFormSheetConfirmComplete.File pointers:
packages/stripe_android/android/src/main/kotlin/com/flutter/stripe/StripeSdkEmbeddedPaymentElementPlatformView.kt("confirm"→result.success(null))packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/EmbeddedPaymentElementView.kt(embeddedPaymentElementFormSheetConfirmCompleteemission)packages/stripe/lib/src/widgets/embedded_payment_element.dart(handlesembeddedPaymentElementFormSheetConfirmComplete)packages/stripe/lib/src/widgets/embedded_payment_element_controller.dart(confirm()awaits the MethodChannel return)🤖 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/StripeSdkEmbeddedPaymentElementPlatformView.kt` around lines 47 - 61, The Android MethodChannel handler in onMethodCall currently calls viewManager.confirm(embeddedView) and immediately result.success(null), causing EmbeddedPaymentElementController.confirm() to resolve to null; instead, modify the "confirm" case so it triggers viewManager.confirm(embeddedView) but does NOT immediately return null — subscribe for the embeddedPaymentElementFormSheetConfirmComplete event emitted by EmbeddedPaymentElementView and, when that one-time completion payload ({status:..., error?}) arrives, call result.success(payload) (or result.error(...) on failure), ensuring onMethodCall returns the same payload shape that iOS provides and matches EmbeddedPaymentElementController.confirm() expectations; use a one-shot listener/unsubscribe after delivery to avoid leaks.packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/PaymentElementConfig.kt (1)
27-33:⚠️ Potential issue | 🟠 Major | ⚡ Quick winWire
paymentMethodConfigurationIdintoPaymentSheet.IntentConfiguration
- Stripe Android SDK v0.66.0
PaymentSheet.IntentConfigurationsupports both optionalpaymentMethodConfigurationIdandonBehalfOf.packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/PaymentElementConfig.kt(buildsPaymentSheet.IntentConfigurationat lines 27-33) setspaymentMethodTypesandonBehalfOf, but never setspaymentMethodConfigurationId(no references found in the repo), so any provided configuration ID would be ignored.🤖 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/PaymentElementConfig.kt` around lines 27 - 33, The PaymentSheet.IntentConfiguration built in PaymentElementConfig (the return block constructing PaymentSheet.IntentConfiguration) is missing the optional paymentMethodConfigurationId field, so any provided configuration ID in intentConfigurationParams will be ignored; fix this by reading intentConfigurationParams.getString("paymentMethodConfigurationId") (or null if absent) and pass it as the paymentMethodConfigurationId parameter when constructing PaymentSheet.IntentConfiguration alongside the existing paymentMethodTypes and onBehalfOf values.packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/utils/Mappers.kt (1)
559-597:⚠️ Potential issue | 🟠 Major | ⚡ Quick winRemove
DisplayPayNowDetailsfrom thereturn nullcase list so PayNow mapping executes.In the
wheninMappers.kt,NextActionType.DisplayPayNowDetailsis included in the early comma-separated group that ends withnull, -> { return null }(lines ~559-567), making the laterNextActionType.DisplayPayNowDetails -> { type: "paynow", qrCodeUrl: ... }branch unreachable (lines ~592-597).🐛 Proposed fix
NextActionType.CashAppRedirect, NextActionType.BlikAuthorize, NextActionType.UseStripeSdk, - NextActionType.DisplayPayNowDetails, NextActionType.DisplayPromptPayDetails, null, -> { return null }🤖 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/utils/Mappers.kt` around lines 559 - 597, The grouped when-case in Mappers.kt currently lists NextActionType.DisplayPayNowDetails in the early branch that immediately returns null, which makes the later NextActionType.DisplayPayNowDetails -> { nextActionMap.putString("type","paynow"); nextActionMap.putString("qrCodeUrl", ...) } branch unreachable; edit the when expression in the mapping function to remove NextActionType.DisplayPayNowDetails from the comma-separated group that returns null (or move it only to the dedicated branch) so the PayNow mapping for NextActionData.DisplayPayNowDetails executes and populates nextActionMap as intended.packages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/StripeSdkImpl+Embedded.swift (1)
146-168:⚠️ Potential issue | 🟠 Major | ⚡ Quick winPromise may never resolve if
embeddedInstanceis nil.If
embeddedInstanceis nil, the optional chainingself?.embeddedInstance?.confirmdoes nothing and theresolvecallback is never invoked, leaving the JS promise hanging indefinitely.🛡️ Proposed fix: guard and resolve with error if instance missing
`@objc`(confirmEmbeddedPaymentElement:reject:) public func confirmEmbeddedPaymentElement(resolve: `@escaping` RCTPromiseResolveBlock, reject: `@escaping` RCTPromiseRejectBlock) { DispatchQueue.main.async { [weak self] in + guard let embeddedInstance = self?.embeddedInstance else { + resolve(Errors.createError( + ErrorType.Failed, + "No EmbeddedPaymentElement instance — did you call create first?" + )) + return + } - self?.embeddedInstance?.presentingViewController = RCTPresentedViewController() - self?.embeddedInstance?.confirm { result in + embeddedInstance.presentingViewController = RCTPresentedViewController() + embeddedInstance.confirm { result in switch result { case .completed:🤖 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/StripeSdkImpl+Embedded.swift around lines 146 - 168, In confirmEmbeddedPaymentElement, guard that self and self.embeddedInstance are non-nil before calling confirm; if missing, immediately call the promise callback with an error (e.g., call resolve with ["status":"failed","error":"embeddedInstance is nil"] or call reject with an appropriate code/message) so the JS promise never hangs; ensure you still set presentingViewController (if instance exists) and then invoke embeddedInstance.confirm as before.
🧹 Nitpick comments (4)
example/lib/screens/regional_payment_methods/multibanco_screen.dart (1)
75-89: 💤 Low valueAdd const keywords to improve performance.
The
EdgeInsets.all(16)andTextwidgets can be marked const for better performance.♻️ Proposed fix
return ExampleScaffold( title: 'Multibanco', tags: ['Payment method'], - padding: EdgeInsets.all(16), + padding: const EdgeInsets.all(16), children: [ LoadingButton( onPressed: () async { await _pay(context); }, - text: 'Pay with Multibanco', + text: const Text('Pay with Multibanco'), ), ], );Note: This assumes
LoadingButton.textaccepts aWidget. If it accepts aString, ignore theTextwidget change.🤖 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/regional_payment_methods/multibanco_screen.dart` around lines 75 - 89, Wrap the static UI constructors with const to improve rebuild performance: change EdgeInsets.all(16) to const EdgeInsets.all(16) and, if LoadingButton's text parameter accepts a Widget, pass const Text('Pay with Multibanco') instead of a non-const Text; leave the text as a String if LoadingButton.text is typed as String. Update occurrences in the build method of ExampleScaffold/LoadingButton where EdgeInsets.all(16) and the button text are created.packages/stripe/lib/src/widgets/embedded_payment_element.dart (1)
167-175: 💤 Low valueConsider if handler registration in confirm() is necessary.
The
confirmHandlerandconfirmTokenHandlerare already registered ininitStateand kept up-to-date viadidUpdateWidget. Setting them again immediately before callingconfirm()may be redundant unless there's a specific timing issue on the native side.If this is intentional (e.g., to handle race conditions where native code might clear handlers), consider adding a comment explaining why.
🤖 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/embedded_payment_element.dart` around lines 167 - 175, The confirm() method currently re-registers handlers that are already set in initState and maintained in didUpdateWidget: remove the redundant Stripe.instance.setConfirmHandler(...) and Stripe.instance.setConfirmTokenHandler(...) calls from confirm(), or if there is a specific timing/race-condition reason to re-register here, add a concise comment above those lines explaining the native-side issue and why re-registration is required; reference the confirm() method and the initState/didUpdateWidget lifecycle handling of intentConfiguration.confirmHandler and intentConfiguration.confirmTokenHandler when making the change.packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/StripeSdkModule.kt (1)
122-129: ⚡ Quick winConsider cleanup of
serverUpdateContinuationsininvalidate().While
checkoutStateObserversandcheckoutInstancesare properly cleaned up ininvalidate()(lines 182-184), theserverUpdateContinuationsmap is not. If the JS side callscheckoutRunServerUpdateStartbut never callscheckoutRunServerUpdateComplete, the continuation will remain in the map indefinitely. Consider cancelling and clearing these continuations ininvalidate()to prevent potential memory leaks.♻️ Proposed cleanup
override fun invalidate() { super.invalidate() stripeUIManagers.forEach { it.destroy() } stripeUIManagers.clear() checkoutStateObservers.values.forEach { it.cancel() } checkoutStateObservers.clear() + serverUpdateContinuations.values.forEach { it.cancel() } + serverUpdateContinuations.clear() checkoutInstances.clear() }🤖 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/StripeSdkModule.kt` around lines 122 - 129, The serverUpdateContinuations map is not cleaned up in invalidate(), which can leak CancellableContinuation<Result<Unit>> entries if JS never calls checkoutRunServerUpdateComplete; update the invalidate() method to iterate serverUpdateContinuations.values and cancel or resume each continuation with an error (for example continuation.cancel(CancellationException("module invalidated")) or continuation.resumeWith(Result.failure(...))), then clear serverUpdateContinuations; reference the serverUpdateContinuations property and the invalidate() function and ensure this cleanup runs alongside the existing cleanup of checkoutInstances and checkoutStateObservers.packages/stripe_platform_interface/lib/src/models/payment_methods.dart (1)
500-520: 💤 Low valueConsider whether PaymentMethodDataMultibanco is needed.
PaymentMethodDataMultibancois structurally identical toPaymentMethodData(lines 480–498). Themultibancovariant inPaymentMethodParams(line 464) already usesPaymentMethodData, so this new class appears unused. If there's no plan to add Multibanco-specific fields, consider removing this class to reduce duplication.If the class is reserved for future Multibanco-specific extensions, a brief comment explaining that intent would clarify the design.
🤖 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_platform_interface/lib/src/models/payment_methods.dart` around lines 500 - 520, PaymentMethodDataMultibanco is a duplicate of PaymentMethodData and appears unused (see PaymentMethodParams referencing PaymentMethodData); either remove the PaymentMethodDataMultibanco declaration and any references to it (ensuring PaymentMethodParams and serializers continue to use PaymentMethodData and run codegen), or if you intend to reserve it for future Multibanco-specific fields, keep the class but add a concise comment above PaymentMethodDataMultibanco stating it exists for future Multibanco-specific extensions and why it intentionally mirrors PaymentMethodData now (update JSON serializers if necessary).
🤖 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/regional_payment_methods/multibanco_screen.dart`:
- Around line 14-29: The _createPaymentIntent method currently decodes
response.body without validating HTTP status; update it to check
response.statusCode (e.g., ensure 200/201) after the http.post and before
json.decode, and if the status is not successful, throw or return a structured
error (including response.statusCode and response.body) so callers of
_createPaymentIntent can handle failures instead of working with unexpected
decoded error payloads.
- Around line 31-48: The code reads result['clientSecret'] in _pay without
verifying it's present; update _pay to validate the clientSecret returned from
_createPaymentIntent (e.g., check that result != null and result['clientSecret']
is non-null/non-empty) before calling Stripe.instance.confirmPayment, and if
missing show an error via the scaffoldMessenger or return early to avoid passing
null to confirmPayment; reference the clientSecret variable, the
_createPaymentIntent call, and the Stripe.instance.confirmPayment invocation
when making the check and early-exit handling.
In
`@packages/stripe_android/android/src/main/kotlin/com/facebook/react/modules/core/DeviceEventManagerModule.java`:
- Around line 42-52: In emit(String eventName, Object params) the code iterates
prefixChannelMap.keySet() and then calls prefixChannelMap.get(prefix), which can
return null if unregisterChannelForPrefix runs concurrently; change iteration to
prefixChannelMap.entrySet() so you obtain both key and value atomically (check
entry.getKey().startsWith(eventName) or vice versa as appropriate), assign the
MethodChannel from entry.getValue(), and before calling uiThreadHandler.post
ensure the selected MethodChannel is non-null (skip/return if null) so
finalTarget.invokeMethod(...) cannot NPE on the UI thread.
In
`@packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/EmbeddedPaymentElementViewManager.kt`:
- Around line 229-245: In update() inside EmbeddedPaymentElementViewManager,
ensure we emit the completion event when parsing or configuration fails: catch
exceptions from JSONObject(json) and, if buildIntentConfiguration(cfg) returns
null, call the view's emitEmbeddedPaymentElementUpdateComplete(null) (in
addition to the existing android.util.Log.e) so the code path is consistent with
updateWithCheckout() and the EmbeddedPaymentElementView error path; locate the
logic around JSONObject(json), jsonToWritableMap(...),
buildIntentConfiguration(...) and add the
emitEmbeddedPaymentElementUpdateComplete(null) call in both the catch block and
the branch where intentConfig == null.
In
`@packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/PaymentOptionDisplayDataMapper.kt`:
- Line 167: String.format is using the system default locale when creating hex
color strings in PaymentOptionDisplayDataMapper (e.g., the hexColor assignment
and the similar call further down), which can produce inconsistent output on
some locales; change those String.format calls to use an explicit Locale (use
java.util.Locale.US) so the format specifier "#%06X" always yields a consistent
hex color string across devices. Ensure both occurrences in
PaymentOptionDisplayDataMapper are updated to String.format(Locale.US, ...)
while keeping the existing RGB_MASK and color.toArgb() arguments unchanged.
In
`@packages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/EmbeddedPaymentElementFactory.swift`:
- Around line 228-243: The native iOS code currently emits the confirm outcome
twice by both invoking the Flutter channel ("onConfirmComplete") and returning
the same payload via result(payload) from EmbeddedPaymentElement.confirm's
completion block; pick a single delivery mechanism and remove the duplicate:
either stop calling self.channel.invokeMethod("onConfirmComplete", ...) inside
the completion handler (so the Dart side only receives the outcome via the
method result that EmbeddedPaymentElement.confirm() returns), or stop calling
result(payload) and only send the outcome via "onConfirmComplete" (and ensure
Dart's _handleMethodCall routes/deserializes it to onFormSheetConfirmComplete or
the awaiting call accordingly). Update the code in the confirmation completion
block (the embeddedElement.confirm { confirmResult in ... } callback) to
implement the chosen single path and keep the existing payload construction and
result/error handling logic intact.
In `@packages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe`
Sdk/EmbeddedPaymentElementView.swift:
- Line 85: The code sets
StripeSdkImpl.shared.embeddedInstance?.presentingViewController to a blank
RCTPresentedViewController(), which is not the real topmost presenter; instead
locate the top presenter by calling findViewControllerPresenter(from:) seeded
with a real root (use RCTKeyWindow()?.rootViewController or the embedded view's
window?.rootViewController) and assign that returned UIViewController to
embeddedInstance?.presentingViewController (and element.presentingViewController
where used). Update the assignment site that currently references
RCTPresentedViewController to call findViewControllerPresenter(from: seedRoot)
and use that result for presentation.
In
`@packages/stripe_platform_interface/lib/src/models/payment_methods.freezed.dart`:
- Around line 5351-5417: The _PaymentMethodParamsMultibanco variant is typed to
accept the generic PaymentMethodData but your new concrete Multibanco type is
PaymentMethodDataMultibanco (which no longer implements PaymentMethodData),
causing API inconsistency and downcasting in when/maybeWhen; update the
Multibanco params to use PaymentMethodDataMultibanco everywhere
(constructor/factory, field type, copyWith impl, equality/hashCode,
toJson/fromJson) or alternatively remove the PaymentMethodDataMultibanco class
and regenerate so the params keep PaymentMethodData—make the change for
_PaymentMethodParamsMultibanco and its copyWith implementation
(__$PaymentMethodParamsMultibancoCopyWithImpl) and repeat the same fix for the
other affected Multibanco ranges referenced (lines ~5794-6152).
---
Outside diff comments:
In
`@packages/stripe_android/android/src/main/kotlin/com/flutter/stripe/StripeSdkEmbeddedPaymentElementPlatformView.kt`:
- Around line 31-44: The RN event prefix is global and must be scoped per view
instance: update StripeSdkEmbeddedPaymentElementPlatformView to
register/unregister the DeviceEventManagerModule.RCTDeviceEventEmitter with a
per-view prefix that includes the view's id (e.g.
"embeddedPaymentElement_$viewId") instead of the fixed "embeddedPaymentElement";
change the registration call (where registerChannelForPrefix is invoked) and the
corresponding unregister call in dispose() to use the same formatted prefix
built from the instance's viewId (use the existing viewId/embeddedView
identifiers in the class), and ensure the JS/Dart listener is updated to
subscribe to the matching per-id event name
(flutter.stripe/embedded_payment_element/$viewId) so events are routed and torn
down per-instance.
- Around line 47-61: The Android MethodChannel handler in onMethodCall currently
calls viewManager.confirm(embeddedView) and immediately result.success(null),
causing EmbeddedPaymentElementController.confirm() to resolve to null; instead,
modify the "confirm" case so it triggers viewManager.confirm(embeddedView) but
does NOT immediately return null — subscribe for the
embeddedPaymentElementFormSheetConfirmComplete event emitted by
EmbeddedPaymentElementView and, when that one-time completion payload
({status:..., error?}) arrives, call result.success(payload) (or
result.error(...) on failure), ensuring onMethodCall returns the same payload
shape that iOS provides and matches EmbeddedPaymentElementController.confirm()
expectations; use a one-shot listener/unsubscribe after delivery to avoid leaks.
In
`@packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/PaymentElementConfig.kt`:
- Around line 27-33: The PaymentSheet.IntentConfiguration built in
PaymentElementConfig (the return block constructing
PaymentSheet.IntentConfiguration) is missing the optional
paymentMethodConfigurationId field, so any provided configuration ID in
intentConfigurationParams will be ignored; fix this by reading
intentConfigurationParams.getString("paymentMethodConfigurationId") (or null if
absent) and pass it as the paymentMethodConfigurationId parameter when
constructing PaymentSheet.IntentConfiguration alongside the existing
paymentMethodTypes and onBehalfOf values.
In
`@packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/utils/Mappers.kt`:
- Around line 559-597: The grouped when-case in Mappers.kt currently lists
NextActionType.DisplayPayNowDetails in the early branch that immediately returns
null, which makes the later NextActionType.DisplayPayNowDetails -> {
nextActionMap.putString("type","paynow"); nextActionMap.putString("qrCodeUrl",
...) } branch unreachable; edit the when expression in the mapping function to
remove NextActionType.DisplayPayNowDetails from the comma-separated group that
returns null (or move it only to the dedicated branch) so the PayNow mapping for
NextActionData.DisplayPayNowDetails executes and populates nextActionMap as
intended.
In `@packages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe`
Sdk/StripeSdkImpl+Embedded.swift:
- Around line 146-168: In confirmEmbeddedPaymentElement, guard that self and
self.embeddedInstance are non-nil before calling confirm; if missing,
immediately call the promise callback with an error (e.g., call resolve with
["status":"failed","error":"embeddedInstance is nil"] or call reject with an
appropriate code/message) so the JS promise never hangs; ensure you still set
presentingViewController (if instance exists) and then invoke
embeddedInstance.confirm as before.
---
Nitpick comments:
In `@example/lib/screens/regional_payment_methods/multibanco_screen.dart`:
- Around line 75-89: Wrap the static UI constructors with const to improve
rebuild performance: change EdgeInsets.all(16) to const EdgeInsets.all(16) and,
if LoadingButton's text parameter accepts a Widget, pass const Text('Pay with
Multibanco') instead of a non-const Text; leave the text as a String if
LoadingButton.text is typed as String. Update occurrences in the build method of
ExampleScaffold/LoadingButton where EdgeInsets.all(16) and the button text are
created.
In
`@packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/StripeSdkModule.kt`:
- Around line 122-129: The serverUpdateContinuations map is not cleaned up in
invalidate(), which can leak CancellableContinuation<Result<Unit>> entries if JS
never calls checkoutRunServerUpdateComplete; update the invalidate() method to
iterate serverUpdateContinuations.values and cancel or resume each continuation
with an error (for example continuation.cancel(CancellationException("module
invalidated")) or continuation.resumeWith(Result.failure(...))), then clear
serverUpdateContinuations; reference the serverUpdateContinuations property and
the invalidate() function and ensure this cleanup runs alongside the existing
cleanup of checkoutInstances and checkoutStateObservers.
In `@packages/stripe_platform_interface/lib/src/models/payment_methods.dart`:
- Around line 500-520: PaymentMethodDataMultibanco is a duplicate of
PaymentMethodData and appears unused (see PaymentMethodParams referencing
PaymentMethodData); either remove the PaymentMethodDataMultibanco declaration
and any references to it (ensuring PaymentMethodParams and serializers continue
to use PaymentMethodData and run codegen), or if you intend to reserve it for
future Multibanco-specific fields, keep the class but add a concise comment
above PaymentMethodDataMultibanco stating it exists for future
Multibanco-specific extensions and why it intentionally mirrors
PaymentMethodData now (update JSON serializers if necessary).
In `@packages/stripe/lib/src/widgets/embedded_payment_element.dart`:
- Around line 167-175: The confirm() method currently re-registers handlers that
are already set in initState and maintained in didUpdateWidget: remove the
redundant Stripe.instance.setConfirmHandler(...) and
Stripe.instance.setConfirmTokenHandler(...) calls from confirm(), or if there is
a specific timing/race-condition reason to re-register here, add a concise
comment above those lines explaining the native-side issue and why
re-registration is required; reference the confirm() method and the
initState/didUpdateWidget lifecycle handling of
intentConfiguration.confirmHandler and intentConfiguration.confirmTokenHandler
when making the change.
🪄 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: 85c976e5-f4f9-4e09-bbd3-a55f8585a777
📒 Files selected for processing (87)
docs.jsondocs/multibanco.mdxexample/lib/screens/payment_sheet/embedded_payment_element_screen.dartexample/lib/screens/regional_payment_methods/multibanco_screen.dartexample/lib/screens/screens.dartpackages/stripe/lib/src/stripe.dartpackages/stripe/lib/src/widgets/embedded_payment_element.dartpackages/stripe_android/android/build.gradlepackages/stripe_android/android/src/main/kotlin/com/facebook/react/modules/core/DeviceEventManagerModule.javapackages/stripe_android/android/src/main/kotlin/com/facebook/react/modules/systeminfo/ReactNativeVersion.ktpackages/stripe_android/android/src/main/kotlin/com/facebook/react/viewmanagers/EmbeddedPaymentElementViewManagerInterface.javapackages/stripe_android/android/src/main/kotlin/com/flutter/stripe/StripeSdkEmbeddedPaymentElementPlatformView.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/AuBECSDebitFormView.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/AuBECSDebitFormViewManager.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/CardFieldView.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/CardFormView.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/EmbeddedPaymentElementView.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/EmbeddedPaymentElementViewManager.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/EventEmitterCompat.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/FinancialConnectionsSheetManager.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/GooglePayButtonManager.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/GooglePayButtonView.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/GooglePayLauncherManager.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/NativeStripeSdkModuleSpec.javapackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/NavigationBarView.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/PaymentElementConfig.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/PaymentLauncherManager.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/PaymentMethodCreateParamsFactory.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/PaymentOptionDisplayDataMapper.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/StripeAbstractComposeView.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/StripeSdkModule.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/addresssheet/AddressLauncherManager.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/addresssheet/AddressSheetView.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/customersheet/CustomerSheetManager.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/customersheet/ReactNativeCustomerAdapter.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/customersheet/ReactNativeCustomerSessionProvider.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/pushprovisioning/AddToWalletButtonManager.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/pushprovisioning/AddToWalletButtonView.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/utils/CheckoutAddressUpdate.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/utils/CheckoutMappers.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/utils/DefaultActivityLifecycleCallbacks.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/utils/Errors.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/utils/Extensions.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/utils/Mappers.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/utils/PostalCodeUtilities.ktpackages/stripe_ios/ios/stripe_ios.podspecpackages/stripe_ios/ios/stripe_ios/Package.swiftpackages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/EmbeddedPaymentElementFactory.swiftpackages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/RCTBridge.swiftpackages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/AddressSheet/AddressSheetUtils.swiftpackages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/AddressSheet/AddressSheetView.swiftpackages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/ApplePayUtils.swiftpackages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/ApplePayViewController.swiftpackages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/ConnectAccountOnboarding/ConnectAccountOnboardingViewController.swiftpackages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/ConnectAccountOnboarding/ConnectAccountOnboardingViewManager.swiftpackages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/EmbeddedPaymentElementView.swiftpackages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/Mappers+Checkout.swiftpackages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/Mappers.swiftpackages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/PaymentMethodFactory.swiftpackages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/PaymentOptionDisplayData+ReactNative.swiftpackages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/PaymentSheetAppearance.swiftpackages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/StripeSdkEmitter.swiftpackages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/StripeSdkImpl+Checkout.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/Stripe Sdk/UIColorExtension.swiftpackages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/pushprovisioning/AddToWalletButtonView.swiftpackages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/pushprovisioning/PushProvisioningUtils.swiftpackages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/StripePlugin.swiftpackages/stripe_ios/ios/stripe_ios/Sources/stripe_objc/Stripe Sdk/StripeSdk.hpackages/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/method_channel_stripe.dartpackages/stripe_platform_interface/lib/src/models/ach_params.g.dartpackages/stripe_platform_interface/lib/src/models/payment_methods.dartpackages/stripe_platform_interface/lib/src/models/payment_methods.freezed.dartpackages/stripe_platform_interface/lib/src/models/payment_methods.g.dartpackages/stripe_platform_interface/lib/src/models/payment_sheet.dartpackages/stripe_platform_interface/lib/src/models/radar_session.dartpackages/stripe_platform_interface/lib/src/models/setup_intent.g.dartpackages/stripe_platform_interface/lib/src/stripe_platform_interface.dartpackages/stripe_platform_interface/test/test_data.dartpackages/stripe_web/lib/src/web_stripe.dart
| Future<Map<String, dynamic>> _createPaymentIntent() async { | ||
| final url = Uri.parse('$kApiUrl/create-payment-intent'); | ||
| final response = await http.post( | ||
| url, | ||
| headers: { | ||
| 'Content-Type': 'application/json', | ||
| }, | ||
| body: json.encode({ | ||
| 'currency': 'eur', | ||
| 'payment_method_types': ['multibanco'], | ||
| 'amount': 1099 | ||
| }), | ||
| ); | ||
|
|
||
| return json.decode(response.body); | ||
| } |
There was a problem hiding this comment.
Validate HTTP response status before decoding.
The method doesn't check the HTTP response status code. A 4xx or 5xx response could contain error JSON that gets silently decoded and used, leading to unexpected behavior or crashes downstream.
🛡️ Proposed fix to add status validation
final response = await http.post(
url,
headers: {
'Content-Type': 'application/json',
},
body: json.encode({
'currency': 'eur',
'payment_method_types': ['multibanco'],
'amount': 1099
}),
);
+ if (response.statusCode != 200) {
+ throw Exception('Failed to create payment intent: ${response.body}');
+ }
return json.decode(response.body);🤖 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/regional_payment_methods/multibanco_screen.dart` around
lines 14 - 29, The _createPaymentIntent method currently decodes response.body
without validating HTTP status; update it to check response.statusCode (e.g.,
ensure 200/201) after the http.post and before json.decode, and if the status is
not successful, throw or return a structured error (including
response.statusCode and response.body) so callers of _createPaymentIntent can
handle failures instead of working with unexpected decoded error payloads.
| Future<void> _pay(BuildContext context) async { | ||
| // 1. Create payment intent on the backend. | ||
| final scaffoldMessenger = ScaffoldMessenger.of(context); | ||
| final result = await _createPaymentIntent(); | ||
| final clientSecret = result['clientSecret']; | ||
|
|
||
| // 2. Use the client secret to confirm the payment. | ||
| try { | ||
| await Stripe.instance.confirmPayment( | ||
| paymentIntentClientSecret: clientSecret, | ||
| data: PaymentMethodParams.multibanco( | ||
| paymentMethodData: PaymentMethodData( | ||
| billingDetails: BillingDetails( | ||
| email: 'multibanco@example.com', | ||
| ), | ||
| ), | ||
| ), | ||
| ); |
There was a problem hiding this comment.
Add null safety check for clientSecret.
The code accesses result['clientSecret'] without verifying it exists. If the backend response is malformed or missing this field, the app will crash when passing null to confirmPayment.
🛡️ Proposed fix to add null check
final scaffoldMessenger = ScaffoldMessenger.of(context);
final result = await _createPaymentIntent();
- final clientSecret = result['clientSecret'];
+ final clientSecret = result['clientSecret'];
+ if (clientSecret == null) {
+ throw Exception('Client secret not found in response');
+ }
// 2. Use the client secret to confirm the payment.🤖 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/regional_payment_methods/multibanco_screen.dart` around
lines 31 - 48, The code reads result['clientSecret'] in _pay without verifying
it's present; update _pay to validate the clientSecret returned from
_createPaymentIntent (e.g., check that result != null and result['clientSecret']
is non-null/non-empty) before calling Stripe.instance.confirmPayment, and if
missing show an error via the scaffoldMessenger or return early to avoid passing
null to confirmPayment; reference the clientSecret variable, the
_createPaymentIntent call, and the Stripe.instance.confirmPayment invocation
when making the check and early-exit handling.
| public void emit(String eventName, Object params) { | ||
| uiThreadHandler.post(() -> channel.invokeMethod(eventName, params)); | ||
| MethodChannel target = channel; | ||
| for (String prefix : prefixChannelMap.keySet()) { | ||
| if (eventName.startsWith(prefix)) { | ||
| target = prefixChannelMap.get(prefix); | ||
| break; | ||
| } | ||
| } | ||
| final MethodChannel finalTarget = target; | ||
| uiThreadHandler.post(() -> finalTarget.invokeMethod(eventName, params)); | ||
| } |
There was a problem hiding this comment.
Possible NPE: get(prefix) can return null after a concurrent unregister.
You iterate prefixChannelMap.keySet() and then call prefixChannelMap.get(prefix) separately. If another thread calls unregisterChannelForPrefix(prefix) between the startsWith check and the get, then target becomes null and finalTarget.invokeMethod(...) will throw an NPE on the UI thread. Iterate the entrySet() to read the value atomically with the key.
🔒 Proposed fix
- public void emit(String eventName, Object params) {
- MethodChannel target = channel;
- for (String prefix : prefixChannelMap.keySet()) {
- if (eventName.startsWith(prefix)) {
- target = prefixChannelMap.get(prefix);
- break;
- }
- }
- final MethodChannel finalTarget = target;
- uiThreadHandler.post(() -> finalTarget.invokeMethod(eventName, params));
+ public void emit(String eventName, Object params) {
+ MethodChannel target = channel;
+ for (java.util.Map.Entry<String, MethodChannel> entry : prefixChannelMap.entrySet()) {
+ if (eventName.startsWith(entry.getKey())) {
+ target = entry.getValue();
+ break;
+ }
+ }
+ final MethodChannel finalTarget = target;
+ uiThreadHandler.post(() -> finalTarget.invokeMethod(eventName, params));
}🤖 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/facebook/react/modules/core/DeviceEventManagerModule.java`
around lines 42 - 52, In emit(String eventName, Object params) the code iterates
prefixChannelMap.keySet() and then calls prefixChannelMap.get(prefix), which can
return null if unregisterChannelForPrefix runs concurrently; change iteration to
prefixChannelMap.entrySet() so you obtain both key and value atomically (check
entry.getKey().startsWith(eventName) or vice versa as appropriate), assign the
MethodChannel from entry.getValue(), and before calling uiThreadHandler.post
ensure the selected MethodChannel is non-null (skip/return if null) so
finalTarget.invokeMethod(...) cannot NPE on the UI thread.
|
|
||
| spanStyle.color.takeIf { it != Color.Unspecified }?.let { color -> | ||
| val hexColor = String.format("#%06X", 0xFFFFFF and color.toArgb()) | ||
| val hexColor = String.format("#%06X", RGB_MASK and color.toArgb()) |
There was a problem hiding this comment.
Specify locale for String.format to ensure consistent hex color output.
The String.format calls use the system default locale, which could produce inconsistent results on devices with different locale settings. RGB hex colors should always format consistently.
🌐 Proposed fix using explicit locale
- val hexColor = String.format("#%06X", RGB_MASK and color.toArgb())
+ val hexColor = String.format(java.util.Locale.US, "#%06X", RGB_MASK and color.toArgb())- val hexBgColor = String.format("#%06X", RGB_MASK and bgColor.toArgb())
+ val hexBgColor = String.format(java.util.Locale.US, "#%06X", RGB_MASK and bgColor.toArgb())Also applies to: 186-186
🧰 Tools
🪛 detekt (1.23.8)
[warning] 167-167: String.format("#%06X", RGB_MASK and color.toArgb()) uses implicitly default locale for string formatting.
(detekt.potential-bugs.ImplicitDefaultLocale)
🤖 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/PaymentOptionDisplayDataMapper.kt`
at line 167, String.format is using the system default locale when creating hex
color strings in PaymentOptionDisplayDataMapper (e.g., the hexColor assignment
and the similar call further down), which can produce inconsistent output on
some locales; change those String.format calls to use an explicit Locale (use
java.util.Locale.US) so the format specifier "#%06X" always yields a consistent
hex color string across devices. Ensure both occurrences in
PaymentOptionDisplayDataMapper are updated to String.format(Locale.US, ...)
while keeping the existing RGB_MASK and color.toArgb() arguments unchanged.
| embeddedElement.confirm { confirmResult in | ||
| let payload: [String: Any] | ||
| switch confirmResult { | ||
| case .completed: | ||
| payload = ["status": "completed"] | ||
| case .canceled: | ||
| payload = ["status": "canceled"] | ||
| case .failed(let error): | ||
| payload = [ | ||
| "status": "failed", | ||
| "error": error.localizedDescription, | ||
| ] | ||
| } | ||
| self.channel.invokeMethod("onConfirmComplete", arguments: payload) | ||
| result(payload) | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check how the Dart embedded element widget consumes confirm results.
fd -t f 'embedded_payment_element.dart' | xargs -I{} rg -nC3 -e 'onConfirmComplete' -e "invokeMethod\('confirm'" -e 'confirm\(' {}Repository: flutter-stripe/flutter_stripe
Length of output: 906
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate the dart file(s)
fd -t f 'embedded_payment_element.dart' | head -n 20
# Pick the first match and print the confirm() implementation area
FILE="$(fd -t f 'embedded_payment_element.dart' | head -n 1)"
echo "Using: $FILE"
# Show confirm() implementation
python3 - <<'PY'
import re,sys,os
path=os.popen("fd -t f 'embedded_payment_element.dart' | head -n 1").read().strip()
print("FILE:",path)
PY
# Print around confirm() and around the onConfirmComplete switch case
sed -n '140,220p' "$FILE"
sed -n '220,320p' "$FILE"Repository: flutter-stripe/flutter_stripe
Length of output: 6340
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n --hidden --no-ignore-vcs "onFormSheetConfirmComplete" packages/stripe/lib/src -S
rg -n --hidden --no-ignore-vcs "\.confirm\(\)" packages/stripe/lib/src -S
rg -n --hidden --no-ignore-vcs "EmbeddedPaymentElement.*confirm" packages/stripe/lib/src -S
rg -n --hidden --no-ignore-vcs "embeddedPaymentElement.*confirm" packages/stripe/lib/src -SRepository: flutter-stripe/flutter_stripe
Length of output: 660
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect controller
FILE="packages/stripe/lib/src/widgets/embedded_payment_element_controller.dart"
sed -n '1,200p' "$FILE"
# Inspect callback field + relevant parts of EmbeddedPaymentElement
FILE2="packages/stripe/lib/src/widgets/embedded_payment_element.dart"
sed -n '1,140p' "$FILE2"
sed -n '200,280p' "$FILE2"
# Find all usages of onFormSheetConfirmComplete across the repo
rg -n "onFormSheetConfirmComplete" -S .
# Find usages of EmbeddedPaymentElementController.confirm() / confirm() return handling
rg -n "EmbeddedPaymentElementController" -S packages/stripe | head -n 200
rg -n "\.confirm\(\)" -S packages/stripe/lib/src/widgets | head -n 200
rg -n "\.confirm\(" -S examples test tests integration_design packages | head -n 200 || trueRepository: flutter-stripe/flutter_stripe
Length of output: 12587
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="example/lib/screens/payment_sheet/embedded_payment_element_screen.dart"
sed -n '1,220p' "$FILE"
rg -n "controller\.confirm\(" "$FILE"
rg -n "onFormSheetConfirmComplete" "$FILE"Repository: flutter-stripe/flutter_stripe
Length of output: 5799
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Find where onConfirmComplete is sent on iOS/Android
rg -n "onConfirmComplete" packages/stripe_ios packages/stripe_android -S
# Inspect iOS confirm handler implementation around the snippet
rg -n "embeddedElement\.confirm|invokeMethod\(\"onConfirmComplete\"|invokeMethod\('onConfirmComplete'" packages/stripe_ios/ios/stripe_ios/Sources/stripe_ios -S
rg -n "invokeMethod\(\"onConfirmComplete\"|result\(payload\)|result\(.*payload" packages/stripe_ios/ios/stripe_ios/Sources/stripe_ios -SRepository: flutter-stripe/flutter_stripe
Length of output: 899
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n "onConfirmComplete" packages/stripe_android -S
rg -n "case ['\"]onConfirmComplete['\"]|invokeMethod\\(.*onConfirmComplete" packages/stripe_android -S || true
# Also search confirm() handler returning payload
rg -n "fun confirm|embedded\\.confirm|invoke\\w*\\(\"confirm\"" packages/stripe_android -SRepository: flutter-stripe/flutter_stripe
Length of output: 55
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Look for any native-to-Dart "confirm complete" method invocations on Android
rg -n "invokeMethod\\(.*(onConfirmComplete|onFormSheetConfirmComplete|embeddedPaymentElementFormSheetConfirmComplete)" packages/stripe_android -S || true
# Also search for method names as plain strings
rg -n "(onConfirmComplete|onFormSheetConfirmComplete|embeddedPaymentElementFormSheetConfirmComplete)" packages/stripe_android -S || trueRepository: flutter-stripe/flutter_stripe
Length of output: 239
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/EventEmitterCompat.kt"
nl -ba "$FILE" | sed -n '50,120p'
# Search for where embeddedPaymentElementFormSheetConfirmComplete is emitted during confirm
rg -n "embeddedPaymentElementFormSheetConfirmComplete" packages/stripe_android -S
# Show relevant sections around those references
for f in $(rg -l "embeddedPaymentElementFormSheetConfirmComplete" packages/stripe_android -S); do
echo "---- $f ----"
nl -ba "$f" | sed -n '1,260p'
doneRepository: flutter-stripe/flutter_stripe
Length of output: 116
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/EventEmitterCompat.kt"
echo "---- $FILE ----"
sed -n '50,110p' "$FILE" | cat -n
echo
echo "References to embeddedPaymentElementFormSheetConfirmComplete:"
rg -n "embeddedPaymentElementFormSheetConfirmComplete" packages/stripe_android -S
echo
for f in $(rg -l "embeddedPaymentElementFormSheetConfirmComplete" packages/stripe_android -S); do
echo "---- $f ----"
sed -n '1,260p' "$f" | cat -n
doneRepository: flutter-stripe/flutter_stripe
Length of output: 7565
confirm outcome is emitted both as the confirm() return value and as an onConfirmComplete callback (can cause duplicate handling).
- iOS native code builds a single
payload, callsself.channel.invokeMethod("onConfirmComplete", arguments: payload), and then returns the same payload viaresult(payload). - Dart
EmbeddedPaymentElement.confirm()awaits the'confirm'method result and returns it, while_handleMethodCallroutesonConfirmCompletetoonFormSheetConfirmComplete. - The example screen wires both
onFormSheetConfirmCompleteand awaits_controller.confirm(), so the same confirmation status is surfaced twice for one user action.
Clarify the intended consumption pattern (return value vs callback) or suppress one path to avoid duplicate state transitions.
🤖 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/EmbeddedPaymentElementFactory.swift`
around lines 228 - 243, The native iOS code currently emits the confirm outcome
twice by both invoking the Flutter channel ("onConfirmComplete") and returning
the same payload via result(payload) from EmbeddedPaymentElement.confirm's
completion block; pick a single delivery mechanism and remove the duplicate:
either stop calling self.channel.invokeMethod("onConfirmComplete", ...) inside
the completion handler (so the Dart side only receives the outcome via the
method result that EmbeddedPaymentElement.confirm() returns), or stop calling
result(payload) and only send the outcome via "onConfirmComplete" (and ensure
Dart's _handleMethodCall routes/deserializes it to onFormSheetConfirmComplete or
the awaiting call accordingly). Update the code in the confirmation completion
block (the embeddedElement.confirm { confirmResult in ... } callback) to
implement the chosen single path and keep the existing payload construction and
result/error handling logic intact.
| @JsonSerializable(explicitToJson: true) | ||
| class _PaymentMethodParamsMultibanco implements PaymentMethodParams { | ||
| const _PaymentMethodParamsMultibanco({required this.paymentMethodData, final String? $type}): $type = $type ?? 'Multibanco'; | ||
| factory _PaymentMethodParamsMultibanco.fromJson(Map<String, dynamic> json) => _$PaymentMethodParamsMultibancoFromJson(json); | ||
|
|
||
| /// Paymentmethod data for this paymentmethod. | ||
| @override final PaymentMethodData paymentMethodData; | ||
|
|
||
| @JsonKey(name: 'paymentMethodType') | ||
| final String $type; | ||
|
|
||
|
|
||
| /// Create a copy of PaymentMethodParams | ||
| /// with the given fields replaced by the non-null parameter values. | ||
| @JsonKey(includeFromJson: false, includeToJson: false) | ||
| @pragma('vm:prefer-inline') | ||
| $PaymentMethodDataCopyWith<PaymentMethodData> get copyWith => _$PaymentMethodDataCopyWithImpl<PaymentMethodData>(this as PaymentMethodData, _$identity); | ||
|
|
||
| /// Serializes this PaymentMethodData to a JSON map. | ||
| Map<String, dynamic> toJson(); | ||
| _$PaymentMethodParamsMultibancoCopyWith<_PaymentMethodParamsMultibanco> get copyWith => __$PaymentMethodParamsMultibancoCopyWithImpl<_PaymentMethodParamsMultibanco>(this, _$identity); | ||
|
|
||
| @override | ||
| Map<String, dynamic> toJson() { | ||
| return _$PaymentMethodParamsMultibancoToJson(this, ); | ||
| } | ||
|
|
||
| @override | ||
| bool operator ==(Object other) { | ||
| return identical(this, other) || (other.runtimeType == runtimeType&&other is PaymentMethodData&&(identical(other.billingDetails, billingDetails) || other.billingDetails == billingDetails)&&(identical(other.shippingDetails, shippingDetails) || other.shippingDetails == shippingDetails)&&(identical(other.mandateData, mandateData) || other.mandateData == mandateData)&&const DeepCollectionEquality().equals(other.metadata, metadata)); | ||
| return identical(this, other) || (other.runtimeType == runtimeType&&other is _PaymentMethodParamsMultibanco&&(identical(other.paymentMethodData, paymentMethodData) || other.paymentMethodData == paymentMethodData)); | ||
| } | ||
|
|
||
| @JsonKey(includeFromJson: false, includeToJson: false) | ||
| @override | ||
| int get hashCode => Object.hash(runtimeType,billingDetails,shippingDetails,mandateData,const DeepCollectionEquality().hash(metadata)); | ||
| int get hashCode => Object.hash(runtimeType,paymentMethodData); | ||
|
|
||
| @override | ||
| String toString() { | ||
| return 'PaymentMethodData(billingDetails: $billingDetails, shippingDetails: $shippingDetails, mandateData: $mandateData, metadata: $metadata)'; | ||
| return 'PaymentMethodParams.multibanco(paymentMethodData: $paymentMethodData)'; | ||
| } | ||
|
|
||
|
|
||
| } | ||
|
|
||
| /// @nodoc | ||
| abstract mixin class $PaymentMethodDataCopyWith<$Res> { | ||
| factory $PaymentMethodDataCopyWith(PaymentMethodData value, $Res Function(PaymentMethodData) _then) = _$PaymentMethodDataCopyWithImpl; | ||
| abstract mixin class _$PaymentMethodParamsMultibancoCopyWith<$Res> implements $PaymentMethodParamsCopyWith<$Res> { | ||
| factory _$PaymentMethodParamsMultibancoCopyWith(_PaymentMethodParamsMultibanco value, $Res Function(_PaymentMethodParamsMultibanco) _then) = __$PaymentMethodParamsMultibancoCopyWithImpl; | ||
| @useResult | ||
| $Res call({ | ||
| BillingDetails? billingDetails, ShippingDetails? shippingDetails, MandateData? mandateData, Map<String, String>? metadata | ||
| PaymentMethodData paymentMethodData | ||
| }); | ||
|
|
||
|
|
||
| $BillingDetailsCopyWith<$Res>? get billingDetails;$ShippingDetailsCopyWith<$Res>? get shippingDetails;$MandateDataCopyWith<$Res>? get mandateData; | ||
| $PaymentMethodDataCopyWith<$Res> get paymentMethodData; | ||
|
|
||
| } | ||
| /// @nodoc | ||
| class _$PaymentMethodDataCopyWithImpl<$Res> | ||
| implements $PaymentMethodDataCopyWith<$Res> { | ||
| _$PaymentMethodDataCopyWithImpl(this._self, this._then); | ||
| class __$PaymentMethodParamsMultibancoCopyWithImpl<$Res> | ||
| implements _$PaymentMethodParamsMultibancoCopyWith<$Res> { | ||
| __$PaymentMethodParamsMultibancoCopyWithImpl(this._self, this._then); | ||
|
|
||
| final PaymentMethodData _self; | ||
| final $Res Function(PaymentMethodData) _then; | ||
| final _PaymentMethodParamsMultibanco _self; | ||
| final $Res Function(_PaymentMethodParamsMultibanco) _then; | ||
|
|
||
| /// Create a copy of PaymentMethodData | ||
| /// Create a copy of PaymentMethodParams | ||
| /// with the given fields replaced by the non-null parameter values. | ||
| @pragma('vm:prefer-inline') @override $Res call({Object? billingDetails = freezed,Object? shippingDetails = freezed,Object? mandateData = freezed,Object? metadata = freezed,}) { | ||
| return _then(_self.copyWith( | ||
| billingDetails: freezed == billingDetails ? _self.billingDetails : billingDetails // ignore: cast_nullable_to_non_nullable | ||
| as BillingDetails?,shippingDetails: freezed == shippingDetails ? _self.shippingDetails : shippingDetails // ignore: cast_nullable_to_non_nullable | ||
| as ShippingDetails?,mandateData: freezed == mandateData ? _self.mandateData : mandateData // ignore: cast_nullable_to_non_nullable | ||
| as MandateData?,metadata: freezed == metadata ? _self.metadata : metadata // ignore: cast_nullable_to_non_nullable | ||
| as Map<String, String>?, | ||
| @pragma('vm:prefer-inline') $Res call({Object? paymentMethodData = null,}) { | ||
| return _then(_PaymentMethodParamsMultibanco( | ||
| paymentMethodData: null == paymentMethodData ? _self.paymentMethodData : paymentMethodData // ignore: cast_nullable_to_non_nullable | ||
| as PaymentMethodData, | ||
| )); |
There was a problem hiding this comment.
Align the Multibanco params variant with the new data model.
_PaymentMethodParamsMultibanco still accepts PaymentMethodData, but the newly generated _PaymentMethodDataMultibanco hierarchy does not implement PaymentMethodData. That makes the dedicated Multibanco data type unusable with the new params variant, and the generated when/maybeWhen APIs stay downcast to the generic base type.
Please either change the Multibanco params factory to use PaymentMethodDataMultibanco or remove the dedicated Multibanco data class and regenerate, so the public API is internally consistent.
Also applies to: 5794-6152
🤖 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_platform_interface/lib/src/models/payment_methods.freezed.dart`
around lines 5351 - 5417, The _PaymentMethodParamsMultibanco variant is typed to
accept the generic PaymentMethodData but your new concrete Multibanco type is
PaymentMethodDataMultibanco (which no longer implements PaymentMethodData),
causing API inconsistency and downcasting in when/maybeWhen; update the
Multibanco params to use PaymentMethodDataMultibanco everywhere
(constructor/factory, field type, copyWith impl, equality/hashCode,
toJson/fromJson) or alternatively remove the PaymentMethodDataMultibanco class
and regenerate so the params keep PaymentMethodData—make the change for
_PaymentMethodParamsMultibanco and its copyWith implementation
(__$PaymentMethodParamsMultibancoCopyWithImpl) and repeat the same fix for the
other affected Multibanco ranges referenced (lines ~5794-6152).
There was a problem hiding this comment.
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 (1)
packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/utils/Mappers.kt (1)
559-565:⚠️ Potential issue | 🔴 Critical | ⚡ Quick winRemove duplicate
DisplayPayNowDetailsentry inmapNextAction.
NextActionType.DisplayPayNowDetailsis included in the groupedreturn nullwhenbranch and also handled by its ownNextActionType.DisplayPayNowDetails -> { ... }branch, causing a duplicatewhenentry and blocking the intended mapping.Suggested fix
NextActionType.CashAppRedirect, NextActionType.BlikAuthorize, NextActionType.UseStripeSdk, - NextActionType.DisplayPayNowDetails, NextActionType.DisplayPromptPayDetails, null, -> { return null }🤖 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/utils/Mappers.kt` around lines 559 - 565, The when-branch for mapNextAction contains a duplicate NextActionType.DisplayPayNowDetails: remove DisplayPayNowDetails from the grouped branch that returns null and leave only the dedicated NextActionType.DisplayPayNowDetails -> { ... } case; update the grouped list (which currently includes NextActionType.CashAppRedirect, NextActionType.BlikAuthorize, NextActionType.UseStripeSdk, NextActionType.DisplayPayNowDetails, NextActionType.DisplayPromptPayDetails, null, -> { ... }) to exclude NextActionType.DisplayPayNowDetails so the specific handler in mapNextAction is the only mapping for that enum value.
🧹 Nitpick comments (1)
example/integration_test/payment_method_test.dart (1)
44-44: ⚡ Quick winAvoid hard-coded card expiry years in integration tests.
At Line 44 and the other updated expiry/assertion lines, using a fixed year (
2028) will cause the same CI breakage again once that date passes. Prefer a shared future-safe value reused across test setup and assertions.♻️ Suggested patch
+const _testCardExpiryYear = 2099; + ... - expirationYear: 2028, + expirationYear: _testCardExpiryYear, ... - expect(paymentMethod.card.expYear, equals(2028)); + expect(paymentMethod.card.expYear, equals(_testCardExpiryYear)); ... - expirationYear: 2028, + expirationYear: _testCardExpiryYear, ... - expect(paymentMethod.card.expYear, equals(2028)); + expect(paymentMethod.card.expYear, equals(_testCardExpiryYear)); ... - expirationYear: 2028, + expirationYear: _testCardExpiryYear, ... - expect(paymentMethod.card.expYear, equals(2028)); + expect(paymentMethod.card.expYear, equals(_testCardExpiryYear));Also applies to: 66-66, 81-81, 90-90, 113-113, 122-122, 140-140
🤖 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/integration_test/payment_method_test.dart` at line 44, Tests use a hard-coded expirationYear (2028) in multiple places (e.g., the payment method setup and related assertions) which will break after that date; replace all literal 2028 occurrences with a single shared future-safe value (e.g., a top-level constant or helper like futureYear = DateTime.now().year + N) and update the payment method creation and every assertion that references expirationYear to use that shared symbol so the test stays valid over time.
🤖 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 `@packages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe`
Sdk/StripeSdkImpl+Embedded.swift:
- Around line 92-104: Calls to UI-affecting methods attachEmbedded and
emitLoadingFailed (and the resolve callbacks) are currently executed off the
main thread; wrap those calls in a main-thread context. Inside the Task's
do/catch, after awaiting EmbeddedPaymentElement.create, call await MainActor.run
{ attachEmbedded(embeddedPaymentElement); resolve(nil) } and in the catch call
await MainActor.run { emitLoadingFailed(error: error); resolve(nil) } so
attachEmbedded, emitLoadingFailed and resolve run on the main thread.
- Around line 56-70: The attachEmbedded call (and the subsequent resolve) in
createEmbeddedPaymentElement are running off the main actor and must be executed
on the main thread because attachEmbedded sets UIKit properties and triggers
layout (see attachEmbedded, embeddedPaymentElementDidUpdateHeight, and
EmbeddedPaymentElement.view.systemLayoutSizeFitting); change the Task body to
await MainActor.run { attachEmbedded(embeddedPaymentElement); resolve(nil) } in
the success path and likewise await MainActor.run { resolve(nil) } in the catch
path (after emitLoadingFailed(error:)) so both attachment and resolution happen
on the MainActor.
In `@packages/stripe_platform_interface/lib/src/stripe_platform_interface.dart`:
- Around line 210-211: Change the new abstract declaration of
setConfirmTokenHandler on StripePlatform into a concrete method that throws
UnimplementedError to preserve additive compatibility; locate the StripePlatform
class and replace the abstract signature void
setConfirmTokenHandler(ConfirmTokenHandler? handler); with a concrete
implementation that throws UnimplementedError (so existing downstream custom
implementations don't break), leaving existing overrides in
method_channel_stripe.dart and web_stripe.dart unchanged.
---
Outside diff comments:
In
`@packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/utils/Mappers.kt`:
- Around line 559-565: The when-branch for mapNextAction contains a duplicate
NextActionType.DisplayPayNowDetails: remove DisplayPayNowDetails from the
grouped branch that returns null and leave only the dedicated
NextActionType.DisplayPayNowDetails -> { ... } case; update the grouped list
(which currently includes NextActionType.CashAppRedirect,
NextActionType.BlikAuthorize, NextActionType.UseStripeSdk,
NextActionType.DisplayPayNowDetails, NextActionType.DisplayPromptPayDetails,
null, -> { ... }) to exclude NextActionType.DisplayPayNowDetails so the specific
handler in mapNextAction is the only mapping for that enum value.
---
Nitpick comments:
In `@example/integration_test/payment_method_test.dart`:
- Line 44: Tests use a hard-coded expirationYear (2028) in multiple places
(e.g., the payment method setup and related assertions) which will break after
that date; replace all literal 2028 occurrences with a single shared future-safe
value (e.g., a top-level constant or helper like futureYear =
DateTime.now().year + N) and update the payment method creation and every
assertion that references expirationYear to use that shared symbol so the test
stays valid over time.
🪄 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: 989e373f-8d10-46cb-b49d-3c14f847c0f3
📒 Files selected for processing (88)
docs.jsondocs/multibanco.mdxexample/integration_test/payment_method_test.dartexample/lib/screens/payment_sheet/embedded_payment_element_screen.dartexample/lib/screens/regional_payment_methods/multibanco_screen.dartexample/lib/screens/screens.dartpackages/stripe/lib/src/stripe.dartpackages/stripe/lib/src/widgets/embedded_payment_element.dartpackages/stripe_android/android/build.gradlepackages/stripe_android/android/src/main/kotlin/com/facebook/react/modules/core/DeviceEventManagerModule.javapackages/stripe_android/android/src/main/kotlin/com/facebook/react/modules/systeminfo/ReactNativeVersion.ktpackages/stripe_android/android/src/main/kotlin/com/facebook/react/viewmanagers/EmbeddedPaymentElementViewManagerInterface.javapackages/stripe_android/android/src/main/kotlin/com/flutter/stripe/StripeSdkEmbeddedPaymentElementPlatformView.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/AuBECSDebitFormView.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/AuBECSDebitFormViewManager.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/CardFieldView.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/CardFormView.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/EmbeddedPaymentElementView.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/EmbeddedPaymentElementViewManager.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/EventEmitterCompat.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/FinancialConnectionsSheetManager.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/GooglePayButtonManager.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/GooglePayButtonView.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/GooglePayLauncherManager.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/NativeStripeSdkModuleSpec.javapackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/NavigationBarView.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/PaymentElementConfig.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/PaymentLauncherManager.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/PaymentMethodCreateParamsFactory.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/PaymentOptionDisplayDataMapper.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/StripeAbstractComposeView.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/StripeSdkModule.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/addresssheet/AddressLauncherManager.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/addresssheet/AddressSheetView.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/customersheet/CustomerSheetManager.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/customersheet/ReactNativeCustomerAdapter.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/customersheet/ReactNativeCustomerSessionProvider.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/pushprovisioning/AddToWalletButtonManager.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/pushprovisioning/AddToWalletButtonView.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/utils/CheckoutAddressUpdate.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/utils/CheckoutMappers.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/utils/DefaultActivityLifecycleCallbacks.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/utils/Errors.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/utils/Extensions.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/utils/Mappers.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/utils/PostalCodeUtilities.ktpackages/stripe_ios/ios/stripe_ios.podspecpackages/stripe_ios/ios/stripe_ios/Package.swiftpackages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/EmbeddedPaymentElementFactory.swiftpackages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/RCTBridge.swiftpackages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/AddressSheet/AddressSheetUtils.swiftpackages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/AddressSheet/AddressSheetView.swiftpackages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/ApplePayUtils.swiftpackages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/ApplePayViewController.swiftpackages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/ConnectAccountOnboarding/ConnectAccountOnboardingViewController.swiftpackages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/ConnectAccountOnboarding/ConnectAccountOnboardingViewManager.swiftpackages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/EmbeddedPaymentElementView.swiftpackages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/Mappers+Checkout.swiftpackages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/Mappers.swiftpackages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/PaymentMethodFactory.swiftpackages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/PaymentOptionDisplayData+ReactNative.swiftpackages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/PaymentSheetAppearance.swiftpackages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/StripeSdkEmitter.swiftpackages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/StripeSdkImpl+Checkout.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/Stripe Sdk/UIColorExtension.swiftpackages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/pushprovisioning/AddToWalletButtonView.swiftpackages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/pushprovisioning/PushProvisioningUtils.swiftpackages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/StripePlugin.swiftpackages/stripe_ios/ios/stripe_ios/Sources/stripe_objc/Stripe Sdk/StripeSdk.hpackages/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/method_channel_stripe.dartpackages/stripe_platform_interface/lib/src/models/ach_params.g.dartpackages/stripe_platform_interface/lib/src/models/payment_methods.dartpackages/stripe_platform_interface/lib/src/models/payment_methods.freezed.dartpackages/stripe_platform_interface/lib/src/models/payment_methods.g.dartpackages/stripe_platform_interface/lib/src/models/payment_sheet.dartpackages/stripe_platform_interface/lib/src/models/radar_session.dartpackages/stripe_platform_interface/lib/src/models/setup_intent.g.dartpackages/stripe_platform_interface/lib/src/stripe_platform_interface.dartpackages/stripe_platform_interface/test/test_data.dartpackages/stripe_web/lib/src/web_stripe.dart
✅ Files skipped from review due to trivial changes (23)
- packages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/pushprovisioning/PushProvisioningUtils.swift
- packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/customersheet/ReactNativeCustomerSessionProvider.kt
- packages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/ApplePayViewController.swift
- packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/AuBECSDebitFormView.kt
- packages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/ApplePayUtils.swift
- packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/GooglePayButtonManager.kt
- packages/stripe_ios/ios/stripe_ios/Sources/stripe_objc/Stripe Sdk/StripeSdkEventEmitterCompat.h
- packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/customersheet/ReactNativeCustomerAdapter.kt
- packages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/StripeSdkImpl+CustomerSheet.swift
- packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/pushprovisioning/AddToWalletButtonManager.kt
- packages/stripe_platform_interface/lib/src/models/setup_intent.g.dart
- packages/stripe_platform_interface/lib/src/models/radar_session.dart
- docs/multibanco.mdx
- packages/stripe_ios/ios/stripe_ios/Package.swift
- packages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/UIColorExtension.swift
- packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/GooglePayLauncherManager.kt
- packages/stripe_platform_interface/lib/src/models/payment_sheet.dart
- packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/CardFormView.kt
- docs.json
- packages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/pushprovisioning/AddToWalletButtonView.swift
- packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/utils/Errors.kt
- packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/FinancialConnectionsSheetManager.kt
- packages/stripe_platform_interface/lib/src/models/payment_methods.freezed.dart
🚧 Files skipped from review as they are similar to previous changes (52)
- packages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/ConnectAccountOnboarding/ConnectAccountOnboardingViewManager.swift
- packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/utils/Extensions.kt
- packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/StripeAbstractComposeView.kt
- packages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/PaymentOptionDisplayData+ReactNative.swift
- packages/stripe_platform_interface/lib/src/method_channel_stripe.dart
- packages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/StripeSdkEmitter.swift
- packages/stripe/lib/src/stripe.dart
- packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/AuBECSDebitFormViewManager.kt
- packages/stripe_ios/ios/stripe_ios/Sources/stripe_objc/Stripe Sdk/StripeSdkEventEmitterCompat.m
- packages/stripe_ios/ios/stripe_ios.podspec
- packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/EventEmitterCompat.kt
- packages/stripe_platform_interface/lib/src/models/ach_params.g.dart
- packages/stripe_android/android/src/main/kotlin/com/facebook/react/modules/systeminfo/ReactNativeVersion.kt
- example/lib/screens/screens.dart
- packages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/AddressSheet/AddressSheetUtils.swift
- packages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/StripePlugin.swift
- packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/utils/DefaultActivityLifecycleCallbacks.kt
- packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/addresssheet/AddressLauncherManager.kt
- packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/PaymentSheetAppearance.kt
- packages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/EmbeddedPaymentElementView.swift
- packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/CardFieldView.kt
- packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/NavigationBarView.kt
- example/lib/screens/regional_payment_methods/multibanco_screen.dart
- packages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/EmbeddedPaymentElementFactory.swift
- packages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/ConnectAccountOnboarding/ConnectAccountOnboardingViewController.swift
- packages/stripe_android/android/build.gradle
- packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/utils/CheckoutAddressUpdate.kt
- packages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/PaymentMethodFactory.swift
- packages/stripe_android/android/src/main/kotlin/com/facebook/react/modules/core/DeviceEventManagerModule.java
- packages/stripe_web/lib/src/web_stripe.dart
- packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/PaymentElementConfig.kt
- packages/stripe/lib/src/widgets/embedded_payment_element.dart
- packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/utils/CheckoutMappers.kt
- packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/PaymentLauncherManager.kt
- example/lib/screens/payment_sheet/embedded_payment_element_screen.dart
- packages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/StripeSdkImpl+PaymentSheet.swift
- packages/stripe_android/android/src/main/kotlin/com/flutter/stripe/StripeSdkEmbeddedPaymentElementPlatformView.kt
- packages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/Mappers+Checkout.swift
- packages/stripe_platform_interface/lib/src/models/payment_methods.dart
- packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/pushprovisioning/AddToWalletButtonView.kt
- packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/NativeStripeSdkModuleSpec.java
- packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/customersheet/CustomerSheetManager.kt
- packages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/AddressSheet/AddressSheetView.swift
- packages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/StripeSdkImpl+Checkout.swift
- packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/PaymentSheetManager.kt
- packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/EmbeddedPaymentElementView.kt
- packages/stripe_platform_interface/lib/src/models/payment_methods.g.dart
- packages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/StripeSdkImpl.swift
- packages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/Mappers.swift
- packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/EmbeddedPaymentElementViewManager.kt
- packages/stripe_ios/ios/stripe_ios/Sources/stripe_objc/Stripe Sdk/StripeSdk.h
- packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/StripeSdkModule.kt
| Task { | ||
| do { | ||
| let embeddedPaymentElement = try await EmbeddedPaymentElement.create( | ||
| intentConfiguration: intentConfig, | ||
| configuration: configuration | ||
| ) | ||
| attachEmbedded(embeddedPaymentElement) | ||
| resolve(nil) | ||
| } catch { | ||
| emitLoadingFailed(error: error) | ||
| // Resolve so the JS hook can finish loading; loading errors are | ||
| // surfaced through the embeddedPaymentElementLoadingFailed event. | ||
| resolve(nil) | ||
| } | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check if EmbeddedPaymentElement.create guarantees main-thread callback
# or if other similar code in this repo uses DispatchQueue.main.async after Task
rg -n "attachEmbedded|EmbeddedPaymentElement.create" --type swift -C 3Repository: flutter-stripe/flutter_stripe
Length of output: 5787
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the relevant portion of StripeSdkImpl+Embedded.swift around attachEmbedded
FILE="packages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/StripeSdkImpl+Embedded.swift"
sed -n '1,220p' "$FILE" | nl -ba | sed -n '40,170p'
# Find where emitLoadingFailed/resolve are implemented/annotated
rg -n "func emitLoadingFailed|emitLoadingFailed\\(|func resolve\\(|resolve\\(nil\\)" "$FILE"
# Inspect EmbeddedPaymentElementFactory where attachEmbeddedView is `@MainActor`
FACTORY="packages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/EmbeddedPaymentElementFactory.swift"
sed -n '130,240p' "$FACTORY" | nl -baRepository: flutter-stripe/flutter_stripe
Length of output: 116
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="packages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/StripeSdkImpl+Embedded.swift"
FACTORY="packages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/EmbeddedPaymentElementFactory.swift"
echo "== StripeSdkImpl+Embedded.swift (lines ~1-220) =="
sed -n '1,220p' "$FILE" | cat -n | sed -n '40,170p'
echo
echo "== StripeSdkImpl+Embedded.swift: attachEmbedded definition =="
rg -n "private func attachEmbedded" "$FILE"
rg -n "attachEmbedded\\(" "$FILE"
echo
echo "== StripeSdkImpl+Embedded.swift: emitLoadingFailed/resolve usage =="
rg -n "emitLoadingFailed\\(|func emitLoadingFailed|resolve\\(nil\\)" "$FILE"
echo
echo "== EmbeddedPaymentElementFactory.swift: attachEmbeddedView annotations =="
rg -n "attachEmbeddedView\\(" "$FACTORY"
sed -n '130,230p' "$FACTORY" | cat -n | sed -n '120,140p'Repository: flutter-stripe/flutter_stripe
Length of output: 7480
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FACTORY="packages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/EmbeddedPaymentElementFactory.swift"
# Show the attachEmbeddedView implementation (around the `@MainActor` and method body)
sed -n '160,230p' "$FACTORY" | cat -n
# Also locate any other UI-thread dispatches in StripeSdkImpl+Embedded.swift
SDK="packages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/StripeSdkImpl+Embedded.swift"
rg -n "DispatchQueue\\.main\\.async|MainActor\\.run|`@MainActor`" "$SDK"Repository: flutter-stripe/flutter_stripe
Length of output: 3957
🏁 Script executed:
#!/bin/bash
set -euo pipefail
SDK="packages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/StripeSdkImpl+Embedded.swift"
# Find delegate type/instance used by attachEmbedded
rg -n "embeddedInstanceDelegate" "$SDK"
# Locate where embeddedInstanceDelegate is defined/created (in same file or nearby)
rg -n "EmbeddedPaymentElementDelegate|presentingViewController|embeddedPaymentElementDidUpdateHeight|embeddedPaymentElementDidUpdatePaymentOption" "$SDK"
# Search for the delegate methods implementation in the repo
rg -n "embeddedPaymentElementDidUpdateHeight|embeddedPaymentElementDidUpdatePaymentOption" packages/stripe_ios/ios/stripe_ios/Sources/stripe_ios --type swift
# Search for RCTPresentedViewController usage in the repo
rg -n "RCTPresentedViewController\\(" packages/stripe_ios/ios/stripe_ios/Sources/stripe_ios --type swiftRepository: flutter-stripe/flutter_stripe
Length of output: 3390
🏁 Script executed:
#!/bin/bash
set -euo pipefail
SDK="packages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/StripeSdkImpl+Embedded.swift"
# Display the delegate implementation around the reported line numbers
sed -n '220,340p' "$SDK" | cat -n
# Inspect any class/actor annotations near embeddedInstanceDelegate definition
rg -n "embeddedInstanceDelegate|class StripeSdkEmbeddedPaymentElementDelegate" "$SDK"Repository: flutter-stripe/flutter_stripe
Length of output: 6551
🏁 Script executed:
#!/bin/bash
set -euo pipefail
SDK="packages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/StripeSdkImpl+Embedded.swift"
PLUGIN="packages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/StripePlugin.swift"
echo "== createEmbeddedPaymentElement method signature/context =="
rg -n "createEmbeddedPaymentElement\\(" "$SDK"
# show around the function start (first ~30 lines near match)
MATCH_LINE=$(rg -n "createEmbeddedPaymentElement\\(" "$SDK" | head -n1 | cut -d: -f1)
START=$((MATCH_LINE-15))
END=$((MATCH_LINE+80))
sed -n "${START},${END}p" "$SDK" | cat -n
echo
echo "== Where emitEmbeddedPaymentElementDidUpdateHeight is implemented =="
rg -n "emitEmbeddedPaymentElementDidUpdateHeight" "$SDK" "$PLUGIN"
# show around in StripePlugin.swift if present
LINE=$(rg -n "embeddedPaymentElementDidUpdateHeight" "$PLUGIN" | head -n1 | cut -d: -f1)
START=$((LINE-15))
END=$((LINE+40))
sed -n "${START},${END}p" "$PLUGIN" | cat -n
echo
echo "== emitEmbeddedPaymentElementDidUpdatePaymentOption implementation =="
rg -n "embeddedPaymentElementDidUpdatePaymentOption|emitEmbeddedPaymentElementDidUpdatePaymentOption" "$PLUGIN"
LINE=$(rg -n "embeddedPaymentElementDidUpdatePaymentOption" "$PLUGIN" | head -n1 | cut -d: -f1)
START=$((LINE-15))
END=$((LINE+40))
sed -n "${START},${END}p" "$PLUGIN" | cat -n
echo
echo "== Any `@MainActor/MainActor.run/DispatchQueue.main` around embedded creation? =="
rg -n "`@MainActor`|MainActor\\.run|DispatchQueue\\.main\\.async" "$SDK"Repository: flutter-stripe/flutter_stripe
Length of output: 10584
🏁 Script executed:
#!/bin/bash
set -euo pipefail
REPO_ROOT="."
SDK="packages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/StripeSdkImpl+Embedded.swift"
PLUGIN="packages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/StripePlugin.swift"
PLUGIN_DIR="packages/stripe_ios/ios/stripe_ios/Sources/stripe_ios"
echo "== Find emitter type/properties =="
rg -n "var emitter\\b|emitter\\?" "$SDK" "$PLUGIN_DIR" --type swift
echo
echo "== Inspect StripePlugin.swift: sendEvent/Emitter implementation =="
rg -n "sendEvent\\(withName:|class .*Emitter|struct .*Emitter|emitEmbeddedPaymentElement" "$PLUGIN_DIR" --type swift
# Show the StripePlugin event dispatch implementation around sendEvent
SEND_LINE=$(rg -n "func sendEvent\\(|sendEvent\\(withName:" "$PLUGIN" | head -n1 | cut -d: -f1 || true)
if [[ -n "${SEND_LINE:-}" ]]; then
START=$((SEND_LINE-40)); END=$((SEND_LINE+60))
sed -n "${START},${END}p" "$PLUGIN" | cat -n
fi
echo
echo "== Check if sendEvent is dispatched to main =="
rg -n "DispatchQueue\\.main\\.|`@MainActor`|MainActor\\.run" "$PLUGIN" --type swiftRepository: flutter-stripe/flutter_stripe
Length of output: 18166
🏁 Script executed:
#!/bin/bash
set -euo pipefail
REPO="packages/stripe_ios/ios/stripe_ios/Sources/stripe_ios"
rg -n "EmbeddedPaymentElement\\.create\\(" "$REPO" --type swift -S
echo "---"
# See if any call-sites wrap attachEmbedded in DispatchQueue.main
rg -n "attachEmbedded\\(" "$REPO" --type swift -SRepository: flutter-stripe/flutter_stripe
Length of output: 880
Ensure attachEmbedded runs on the main thread (UIKit/layout)
createEmbeddedPaymentElementcallsattachEmbedded(embeddedPaymentElement)directly from aTask, withoutMainActorisolation.attachEmbeddedsetselement.presentingViewController = RCTPresentedViewController()and immediately callsembeddedInstanceDelegate.embeddedPaymentElementDidUpdateHeight(...), which computes layout viaembeddedPaymentElement.view.systemLayoutSizeFitting(...)(UIKit).sendEvent(...)already hops to the main thread for event delivery, but these UIKit/layout operations still run on theTask’s executor; wrap the attachment (and resolve) inMainActor.
Proposed fix
Task {
do {
let embeddedPaymentElement = try await EmbeddedPaymentElement.create(
intentConfiguration: intentConfig,
configuration: configuration
)
- attachEmbedded(embeddedPaymentElement)
- resolve(nil)
+ await MainActor.run {
+ attachEmbedded(embeddedPaymentElement)
+ resolve(nil)
+ }
} catch {
- emitLoadingFailed(error: error)
- // Resolve so the JS hook can finish loading; loading errors are
- // surfaced through the embeddedPaymentElementLoadingFailed event.
- resolve(nil)
+ await MainActor.run {
+ emitLoadingFailed(error: error)
+ resolve(nil)
+ }
}
}🤖 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/StripeSdkImpl+Embedded.swift around lines 56 - 70, The attachEmbedded call
(and the subsequent resolve) in createEmbeddedPaymentElement are running off the
main actor and must be executed on the main thread because attachEmbedded sets
UIKit properties and triggers layout (see attachEmbedded,
embeddedPaymentElementDidUpdateHeight, and
EmbeddedPaymentElement.view.systemLayoutSizeFitting); change the Task body to
await MainActor.run { attachEmbedded(embeddedPaymentElement); resolve(nil) } in
the success path and likewise await MainActor.run { resolve(nil) } in the catch
path (after emitLoadingFailed(error:)) so both attachment and resolution happen
on the MainActor.
| Task { | ||
| do { | ||
| let embeddedPaymentElement = try await EmbeddedPaymentElement.create( | ||
| intentConfiguration: intentConfig, | ||
| checkout: checkout, | ||
| configuration: configuration | ||
| ) | ||
| embeddedPaymentElement.delegate = embeddedInstanceDelegate | ||
| embeddedPaymentElement.presentingViewController = RCTPresentedViewController() | ||
| self.embeddedInstance = embeddedPaymentElement | ||
|
|
||
| attachEmbedded(embeddedPaymentElement) | ||
| resolve(nil) | ||
|
|
||
| embeddedInstanceDelegate.embeddedPaymentElementDidUpdateHeight(embeddedPaymentElement: embeddedPaymentElement) | ||
| embeddedInstanceDelegate.embeddedPaymentElementDidUpdatePaymentOption(embeddedPaymentElement: embeddedPaymentElement) | ||
| } catch { | ||
| let errorPayload = Errors.createError(ErrorType.Failed, error) | ||
| let errorDetails = errorPayload["error"] as? NSDictionary | ||
| let (message, code) = extractEmbeddedPaymentElementErrorInfo( | ||
| from: errorDetails, | ||
| fallbackMessage: error.localizedDescription, | ||
| fallbackCode: ErrorType.Failed | ||
| ) | ||
| dispatchEmbeddedPaymentElementLoadingFailed( | ||
| message: message, | ||
| code: code, | ||
| details: errorDetails | ||
| ) | ||
| resolve(errorPayload) | ||
| return | ||
| emitLoadingFailed(error: error) | ||
| resolve(nil) | ||
| } | ||
| } |
There was a problem hiding this comment.
Same main-thread concern as createEmbeddedPaymentElement.
The attachEmbedded and event emission calls at lines 98-102 should also be dispatched to the main thread, consistent with the fix suggested for createEmbeddedPaymentElement.
🤖 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/StripeSdkImpl+Embedded.swift around lines 92 - 104, Calls to UI-affecting
methods attachEmbedded and emitLoadingFailed (and the resolve callbacks) are
currently executed off the main thread; wrap those calls in a main-thread
context. Inside the Task's do/catch, after awaiting
EmbeddedPaymentElement.create, call await MainActor.run {
attachEmbedded(embeddedPaymentElement); resolve(nil) } and in the catch call
await MainActor.run { emitLoadingFailed(error: error); resolve(nil) } so
attachEmbedded, emitLoadingFailed and resolve run on the main thread.
| /// Set the confirmation token handler for embedded payment elements | ||
| void setConfirmTokenHandler(ConfirmTokenHandler? handler); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
python - <<'PY'
import pathlib, re
dart_files = list(pathlib.Path(".").rglob("*.dart"))
impls = []
for f in dart_files:
text = f.read_text(encoding="utf-8", errors="ignore")
if re.search(r'\bclass\s+\w+\s+extends\s+StripePlatform\b', text):
impls.append((f, text))
print("StripePlatform implementers:")
for f, _ in impls:
print(f"- {f}")
print("\nOverride status for setConfirmTokenHandler:")
for f, text in impls:
ok = bool(re.search(r'\bvoid\s+setConfirmTokenHandler\s*\(', text))
print(f"{f}: {'OK' if ok else 'MISSING'}")
PYRepository: flutter-stripe/flutter_stripe
Length of output: 636
🏁 Script executed:
#!/bin/bash
python - <<'PY'
import pathlib, re
dart_files = [p for p in pathlib.Path(".").rglob("*.dart") if p.is_file()]
impls = []
for f in dart_files:
text = f.read_text(encoding="utf-8", errors="ignore")
if re.search(r'\bclass\s+\w+\s+extends\s+StripePlatform\b', text):
impls.append((f, text))
print("StripePlatform implementers:")
for f, _ in impls:
print(f"- {f}")
print("\nOverride status for setConfirmTokenHandler:")
for f, text in impls:
ok = bool(re.search(r'\bvoid\s+setConfirmTokenHandler\s*\(', text))
print(f"{f}: {'OK' if ok else 'MISSING'}")
PYRepository: flutter-stripe/flutter_stripe
Length of output: 387
Prevent downstream compile-time break from new abstract StripePlatform method
Even though the repo’s StripePlatform implementations (method_channel_stripe.dart, web_stripe.dart) already override setConfirmTokenHandler, adding it as an abstract method still forces any external downstream StripePlatform implementations to update immediately. Provide a concrete default that throws UnimplementedError to preserve additive compatibility.
💡 Compatibility-preserving shape
- /// Set the confirmation token handler for embedded payment elements
- void setConfirmTokenHandler(ConfirmTokenHandler? handler);
+ /// Set the confirmation token handler for embedded payment elements
+ void setConfirmTokenHandler(ConfirmTokenHandler? handler) {
+ throw UnimplementedError(
+ 'setConfirmTokenHandler() has not been implemented.',
+ );
+ }📝 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.
| /// Set the confirmation token handler for embedded payment elements | |
| void setConfirmTokenHandler(ConfirmTokenHandler? handler); | |
| /// Set the confirmation token handler for embedded payment elements | |
| void setConfirmTokenHandler(ConfirmTokenHandler? handler) { | |
| throw UnimplementedError( | |
| 'setConfirmTokenHandler() has not been implemented.', | |
| ); | |
| } |
🤖 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_platform_interface/lib/src/stripe_platform_interface.dart`
around lines 210 - 211, Change the new abstract declaration of
setConfirmTokenHandler on StripePlatform into a concrete method that throws
UnimplementedError to preserve additive compatibility; locate the StripePlatform
class and replace the abstract signature void
setConfirmTokenHandler(ConfirmTokenHandler? handler); with a concrete
implementation that throws UnimplementedError (so existing downstream custom
implementations don't break), leaving existing overrides in
method_channel_stripe.dart and web_stripe.dart unchanged.
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/stripe_platform_interface/lib/src/models/payment_methods.dart (1)
459-465:⚠️ Potential issue | 🟠 Major | ⚡ Quick winWire
PaymentMethodDataMultibancointoPaymentMethodParams.multibanco(or remove it)
PaymentMethodParams.multibanco(lines 459-465) usesrequired PaymentMethodData paymentMethodData, butPaymentMethodDataMultibancois only defined here (lines ~502-519) and referenced by its own generated code—no other usage—unlikeideal, which correctly usesPaymentMethodDataIdeal.- Either change
PaymentMethodParams.multibancoto usePaymentMethodDataMultibancofor type consistency, or remove the unusedPaymentMethodDataMultibancotype.PaymentMethodmakesMultibanco? multibanconullable while other payment-method-specific fields arerequired; align nullability or document why Multibanco differs.- The “Multibanco” typo warning is not supported:
Multibancois used consistently as a JSON key and inPaymentMethodType.🤖 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_platform_interface/lib/src/models/payment_methods.dart` around lines 459 - 465, PaymentMethodParams.multibanco currently types its field as PaymentMethodData but you’ve defined a dedicated PaymentMethodDataMultibanco type that is never used; update the factory signature for PaymentMethodParams.multibanco to accept required PaymentMethodDataMultibanco paymentMethodData (replacing PaymentMethodData) so the specific Multibanco data shape is wired into the union, or alternatively delete the unused PaymentMethodDataMultibanco type if you intend to keep the generic PaymentMethodData; additionally review the PaymentMethod class where Multibanco? multibanco is nullable and either make it required like the other specific fields or add a comment documenting why Multibanco is optional to keep consistency with the rest of the models.
🤖 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.
Outside diff comments:
In `@packages/stripe_platform_interface/lib/src/models/payment_methods.dart`:
- Around line 459-465: PaymentMethodParams.multibanco currently types its field
as PaymentMethodData but you’ve defined a dedicated PaymentMethodDataMultibanco
type that is never used; update the factory signature for
PaymentMethodParams.multibanco to accept required PaymentMethodDataMultibanco
paymentMethodData (replacing PaymentMethodData) so the specific Multibanco data
shape is wired into the union, or alternatively delete the unused
PaymentMethodDataMultibanco type if you intend to keep the generic
PaymentMethodData; additionally review the PaymentMethod class where Multibanco?
multibanco is nullable and either make it required like the other specific
fields or add a comment documenting why Multibanco is optional to keep
consistency with the rest of the models.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 200e464a-6291-49d6-8cac-09542df1c8c1
📒 Files selected for processing (5)
example/integration_test/payment_method_test.dartpackages/stripe_ios/ios/stripe_ios/Package.swiftpackages/stripe_platform_interface/lib/src/models/payment_methods.dartpackages/stripe_platform_interface/lib/src/models/payment_methods.freezed.dartpackages/stripe_platform_interface/lib/src/models/payment_methods.g.dart
✅ Files skipped from review due to trivial changes (1)
- packages/stripe_platform_interface/lib/src/models/payment_methods.g.dart
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/stripe_ios/ios/stripe_ios/Package.swift
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/stripe_platform_interface/lib/src/models/payment_methods.dart (1)
460-465:⚠️ Potential issue | 🟠 Major | 🏗️ Heavy liftEnforce Multibanco email at the Dart contract boundary.
Lines 462-465 accept generic
PaymentMethodData, and Lines 500-516 keepbillingDetailsoptional. That permits payloads withoutbillingDetails.email, but both native paths require email and throw when it is missing (AndroidPaymentMethodCreateParamsFactory.kt:136-153, iOSPaymentMethodFactory.swift:304-314).Suggested direction
`@JsonSerializable`(explicitToJson: true) `@FreezedUnionValue`('Multibanco') /// Config parameters for Multibanco payment method. const factory PaymentMethodParams.multibanco({ /// Paymentmethod data for this paymentmethod. - required PaymentMethodData paymentMethodData, + required PaymentMethodDataMultibanco paymentMethodData, }) = _PaymentMethodParamsMultibanco;`@freezed` /// Multi banco payment method data abstract class PaymentMethodDataMultibanco with _$PaymentMethodDataMultibanco { - `@JsonSerializable`(explicitToJson: true) - const factory PaymentMethodDataMultibanco({ - /// Billing information. - BillingDetails? billingDetails, + const PaymentMethodDataMultibanco._(); + `@JsonSerializable`(explicitToJson: true) + factory PaymentMethodDataMultibanco({ + /// Billing information (email is required for Multibanco). + required BillingDetails billingDetails, /// Shipping details ShippingDetails? shippingDetails, @@ - Map<String, String>? metadata, - }) = _PaymentMethodDataMultibanco; + Map<String, String>? metadata, + }) { + final email = billingDetails.email; + if (email == null || email.isEmpty) { + throw ArgumentError('Multibanco requires billingDetails.email'); + } + return _PaymentMethodDataMultibanco( + billingDetails: billingDetails, + shippingDetails: shippingDetails, + mandateData: mandateData, + metadata: metadata, + ); + }Also applies to: 500-520
🤖 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_platform_interface/lib/src/models/payment_methods.dart` around lines 460 - 465, The Multibanco factory currently accepts a generic PaymentMethodData which allows missing billingDetails.email even though native Android (PaymentMethodCreateParamsFactory.kt) and iOS (PaymentMethodFactory.swift) require it; update the Dart contract so that PaymentMethodParams.multibanco requires a PaymentMethodData that enforces billingDetails.email (e.g., a dedicated PaymentMethodDataMultibanco type or make billingDetails.email a non-nullable field when constructing the multibanco variant) and mirror the same non-null requirement for the other multibanco-related factory/constructors referenced around lines 500-520; ensure the constructors/Freezed union uses the stricter type or validation so callers cannot create a Multibanco params instance without billingDetails.email.
🧹 Nitpick comments (1)
example/integration_test/app_test.dart (1)
71-71: ⚡ Quick winAvoid another hardcoded expiry rollover in this integration test.
Line 71 still hardcodes a year, so this will age out again. Prefer a relative year to keep the test stable long-term.
Proposed change
final cardDetails = CardDetails( number: '4242424242424242', cvc: '424', expirationMonth: 04, - expirationYear: 2028, + expirationYear: DateTime.now().year + 2, );🤖 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/integration_test/app_test.dart` at line 71, The test currently hardcodes expirationYear: 2028 which will age out; update the integration test in app_test.dart to compute a relative future year instead (e.g., use DateTime.now().year + N) so the expirationYear value is always in the future; locate the object/property setting named expirationYear and replace the literal 2028 with a computed future year based on DateTime.now().year (choose N to keep the card valid for the test duration).
🤖 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 `@packages/stripe_ios/ios/stripe_ios/Package.swift`:
- Line 15: The Package.resolved lockfile is out of sync with the manifest which
now requires stripe-ios-spm exact "25.15.0"; update
packages/stripe_ios/ios/stripe_ios/Package.resolved to pin
"https://github.com/stripe/stripe-ios-spm" at version 25.15.0 (replacing the
current 25.9.0 entry) so dependency resolution matches the Package.swift
manifest.
---
Outside diff comments:
In `@packages/stripe_platform_interface/lib/src/models/payment_methods.dart`:
- Around line 460-465: The Multibanco factory currently accepts a generic
PaymentMethodData which allows missing billingDetails.email even though native
Android (PaymentMethodCreateParamsFactory.kt) and iOS
(PaymentMethodFactory.swift) require it; update the Dart contract so that
PaymentMethodParams.multibanco requires a PaymentMethodData that enforces
billingDetails.email (e.g., a dedicated PaymentMethodDataMultibanco type or make
billingDetails.email a non-nullable field when constructing the multibanco
variant) and mirror the same non-null requirement for the other
multibanco-related factory/constructors referenced around lines 500-520; ensure
the constructors/Freezed union uses the stricter type or validation so callers
cannot create a Multibanco params instance without billingDetails.email.
---
Nitpick comments:
In `@example/integration_test/app_test.dart`:
- Line 71: The test currently hardcodes expirationYear: 2028 which will age out;
update the integration test in app_test.dart to compute a relative future year
instead (e.g., use DateTime.now().year + N) so the expirationYear value is
always in the future; locate the object/property setting named expirationYear
and replace the literal 2028 with a computed future year based on
DateTime.now().year (choose N to keep the card valid for the test duration).
🪄 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: 5bced921-1765-41c6-87f5-2da983007f31
📒 Files selected for processing (6)
example/integration_test/app_test.dartexample/integration_test/payment_method_test.dartpackages/stripe_ios/ios/stripe_ios/Package.swiftpackages/stripe_platform_interface/lib/src/models/payment_methods.dartpackages/stripe_platform_interface/lib/src/models/payment_methods.freezed.dartpackages/stripe_platform_interface/lib/src/models/payment_methods.g.dart
🚧 Files skipped from review as they are similar to previous changes (2)
- example/integration_test/payment_method_test.dart
- packages/stripe_platform_interface/lib/src/models/payment_methods.g.dart
| .library(name: "stripe-ios", targets: ["stripe_ios"]) | ||
| ], | ||
| dependencies: [ | ||
| .package(url: "https://github.com/stripe/stripe-ios-spm", exact: "25.15.0"), |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python - <<'PY'
import json
import pathlib
import re
pkg = pathlib.Path("packages/stripe_ios/ios/stripe_ios/Package.swift")
resolved = pathlib.Path("packages/stripe_ios/ios/stripe_ios/Package.resolved")
manifest = pkg.read_text(encoding="utf-8")
m = re.search(r'stripe-ios-spm",\s*exact:\s*"([^"]+)"', manifest)
manifest_version = m.group(1) if m else None
lock = json.loads(resolved.read_text(encoding="utf-8"))
resolved_version = None
for pin in lock.get("pins", []):
if pin.get("identity") == "stripe-ios-spm":
resolved_version = pin.get("state", {}).get("version")
break
print(f"Package.swift version: {manifest_version}")
print(f"Package.resolved version: {resolved_version}")
print("MATCH" if manifest_version == resolved_version else "MISMATCH")
PYRepository: flutter-stripe/flutter_stripe
Length of output: 149
Sync SwiftPM lockfile with the manifest bump (stripe-ios-spm).
.package(url: "https://github.com/stripe/stripe-ios-spm", exact: "25.15.0"),
packages/stripe_ios/ios/stripe_ios/Package.swift now requires stripe-ios-spm 25.15.0, but packages/stripe_ios/ios/stripe_ios/Package.resolved still pins 25.9.0, creating drift that can lead to inconsistent dependency resolution across environments. Update Package.resolved to 25.15.0.
🤖 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/Package.swift` at line 15, The
Package.resolved lockfile is out of sync with the manifest which now requires
stripe-ios-spm exact "25.15.0"; update
packages/stripe_ios/ios/stripe_ios/Package.resolved to pin
"https://github.com/stripe/stripe-ios-spm" at version 25.15.0 (replacing the
current 25.9.0 entry) so dependency resolution matches the Package.swift
manifest.
Stripe sync v 0.66
Summary by CodeRabbit
New Features
Bug Fixes
Documentation