Repository: stripe/stripe-android
Pull request #10689: Embedded Change UI
Author: login: tianzhao-stripe
Created: 2025-04-22T00:51:22Z | Merged: 2025-04-23T19:29:57Z

# Summary
Added Change UI trailing content to PaymentMethodEmbeddedLayoutUi

# Motivation
https://jira.corp.stripe.com/browse/MOBILESDK-3445

Let users change their new payment method (Card or US BankAccount) when Embedded form action is continue

# Testing
<!-- How was the code tested? Be as specific as possible. -->
- [X] Added tests
- [X] Modified tests
- [X] Manually verified

# Screenshots
|| Flat With Radio  | Flat With Checkmark | Floating Button | 
|---|---|---|---|
|Dark, US Bank Account|![1000000274](https://github.com/user-attachments/assets/af765477-663f-4f2d-90b6-e65d2157d2bb)|![1000000272](https://github.com/user-attachments/assets/ede0e390-c5ff-4ff9-8042-d9204b9bdd17)|![1000000271](https://github.com/user-attachments/assets/85cd4862-8748-408e-a59d-0d994a2e6443)|
|Light, Card|![1000000275](https://github.com/user-attachments/assets/e690ce97-7206-450d-8216-9fee198d814a)|![1000000276](https://github.com/user-attachments/assets/1bd66a9b-8066-479b-8974-24f614e11605)|![1000000277](https://github.com/user-attachments/assets/e2d48afa-c69e-4435-beaf-63c636d67d38)|


# Changelog
N.A.


Diff:
diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/verticalmode/NewPaymentMethodRowButton.kt b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/verticalmode/NewPaymentMethodRowButton.kt
index b0ca36abaf9..6a9c4e908e4 100644
--- a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/verticalmode/NewPaymentMethodRowButton.kt
+++ b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/verticalmode/NewPaymentMethodRowButton.kt
@@ -2,6 +2,7 @@ package com.stripe.android.paymentsheet.verticalmode
 
 import androidx.annotation.RestrictTo
 import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.foundation.layout.RowScope
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.width
 import androidx.compose.runtime.Composable
@@ -27,6 +28,7 @@ internal fun NewPaymentMethodRowButton(
     displayablePaymentMethod: DisplayablePaymentMethod,
     imageLoader: StripeImageLoader,
     modifier: Modifier = Modifier,
+    trailingContent: (@Composable RowScope.() -> Unit)? = null,
     rowStyle: Embedded.RowStyle = Embedded.RowStyle.FloatingButton.default
 ) {
     val iconUrl = if (isSystemInDarkTheme() && displayablePaymentMethod.darkThemeIconUrl != null) {
@@ -48,7 +50,8 @@ internal fun NewPaymentMethodRowButton(
             displayablePaymentMethod.onClick()
         },
         modifier = modifier.testTag("${TEST_TAG_NEW_PAYMENT_METHOD_ROW_BUTTON}_${displayablePaymentMethod.code}"),
-        rowStyle = rowStyle
+        rowStyle = rowStyle,
+        trailingContent = trailingContent,
     )
 }
 
@@ -66,6 +69,7 @@ internal fun NewPaymentMethodRowButton(
     iconRequiresTinting: Boolean,
     onClick: () -> Unit,
     modifier: Modifier = Modifier,
+    trailingContent: (@Composable RowScope.() -> Unit)? = null,
     rowStyle: Embedded.RowStyle = Embedded.RowStyle.FloatingButton.default
 ) {
     PaymentMethodRowButton(
@@ -87,6 +91,7 @@ internal fun NewPaymentMethodRowButton(
         promoText = promoText,
         onClick = onClick,
         modifier = modifier,
-        style = rowStyle
+        style = rowStyle,
+        trailingContent = trailingContent,
     )
 }
diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/verticalmode/PaymentMethodEmbeddedLayoutUI.kt b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/verticalmode/PaymentMethodEmbeddedLayoutUI.kt
index b6ee2c05ae6..51d4775c883 100644
--- a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/verticalmode/PaymentMethodEmbeddedLayoutUI.kt
+++ b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/verticalmode/PaymentMethodEmbeddedLayoutUI.kt
@@ -5,21 +5,33 @@ import androidx.compose.foundation.isSystemInDarkTheme
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.ColumnScope
+import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.wrapContentHeight
 import androidx.compose.material.Divider
+import androidx.compose.material.Icon
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.font.FontWeight
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 import com.stripe.android.core.strings.ResolvableString
+import com.stripe.android.core.strings.resolvableString
+import com.stripe.android.model.PaymentMethod
 import com.stripe.android.paymentelement.ExperimentalEmbeddedPaymentElementApi
 import com.stripe.android.paymentsheet.DisplayableSavedPaymentMethod
 import com.stripe.android.paymentsheet.PaymentSheet.Appearance.Embedded
+import com.stripe.android.paymentsheet.R
 import com.stripe.android.ui.core.elements.Mandate
 import com.stripe.android.uicore.image.StripeImageLoader
 import com.stripe.android.uicore.strings.resolve
@@ -103,45 +115,26 @@ internal fun PaymentMethodEmbeddedLayoutUI(
     }
     Column(modifier = modifier, verticalArrangement = arrangement) {
         if (rowStyle.topSeparatorEnabled()) OptionalEmbeddedDivider(rowStyle)
-        if (displayedSavedPaymentMethod != null) {
-            SavedPaymentMethodRowButton(
-                displayableSavedPaymentMethod = displayedSavedPaymentMethod,
-                isEnabled = isEnabled,
-                isSelected = selection?.isSaved == true,
-                trailingContent = {
-                    SavedPaymentMethodTrailingContent(
-                        savedPaymentMethodAction = savedPaymentMethodAction,
-                        onViewMorePaymentMethods = onViewMorePaymentMethods,
-                        onManageOneSavedPaymentMethod = { onManageOneSavedPaymentMethod(displayedSavedPaymentMethod) },
-                    )
-                },
-                onClick = { onSelectSavedPaymentMethod(displayedSavedPaymentMethod) },
-                rowStyle = rowStyle
-            )
 
-            if (paymentMethods.isNotEmpty()) OptionalEmbeddedDivider(rowStyle)
-        }
-
-        val selectedIndex = remember(selection, paymentMethods) {
-            if (selection is PaymentMethodVerticalLayoutInteractor.Selection.New) {
-                val code = selection.code
-                paymentMethods.indexOfFirst { it.code == code }
-            } else {
-                -1
-            }
-        }
-
-        paymentMethods.forEachIndexed { index, item ->
-            NewPaymentMethodRowButton(
-                isEnabled = isEnabled,
-                isSelected = index == selectedIndex,
-                displayablePaymentMethod = item,
-                imageLoader = imageLoader,
-                rowStyle = rowStyle
-            )
+        EmbeddedSavedPaymentMethodRowButton(
+            paymentMethods = paymentMethods,
+            displayedSavedPaymentMethod = displayedSavedPaymentMethod,
+            savedPaymentMethodAction = savedPaymentMethodAction,
+            selection = selection,
+            isEnabled = isEnabled,
+            onViewMorePaymentMethods = onViewMorePaymentMethods,
+            onManageOneSavedPaymentMethod = onManageOneSavedPaymentMethod,
+            onSelectSavedPaymentMethod = onSelectSavedPaymentMethod,
+            rowStyle = rowStyle
+        )
 
-            if (index != paymentMethods.lastIndex) OptionalEmbeddedDivider(rowStyle)
-        }
+        EmbeddedNewPaymentMethodRowButtonsLayoutUi(
+            paymentMethods = paymentMethods,
+            selection = selection,
+            isEnabled = isEnabled,
+            imageLoader = imageLoader,
+            rowStyle = rowStyle,
+        )
 
         if (rowStyle.bottomSeparatorEnabled()) OptionalEmbeddedDivider(rowStyle)
     }
@@ -233,3 +226,129 @@ private fun Embedded.RowStyle.endSeparatorInset(): Dp {
         is Embedded.RowStyle.FlatWithCheckmark -> endSeparatorInsetDp.dp
     }
 }
+
+@OptIn(ExperimentalEmbeddedPaymentElementApi::class)
+@Composable
+internal fun EmbeddedSavedPaymentMethodRowButton(
+    paymentMethods: List<DisplayablePaymentMethod>,
+    displayedSavedPaymentMethod: DisplayableSavedPaymentMethod?,
+    savedPaymentMethodAction: PaymentMethodVerticalLayoutInteractor.SavedPaymentMethodAction,
+    selection: PaymentMethodVerticalLayoutInteractor.Selection?,
+    isEnabled: Boolean,
+    onViewMorePaymentMethods: () -> Unit,
+    onManageOneSavedPaymentMethod: (DisplayableSavedPaymentMethod) -> Unit,
+    onSelectSavedPaymentMethod: (DisplayableSavedPaymentMethod) -> Unit,
+    rowStyle: Embedded.RowStyle,
+) {
+    if (displayedSavedPaymentMethod != null) {
+        SavedPaymentMethodRowButton(
+            displayableSavedPaymentMethod = displayedSavedPaymentMethod,
+            isEnabled = isEnabled,
+            isSelected = selection?.isSaved == true,
+            trailingContent = {
+                SavedPaymentMethodTrailingContent(
+                    savedPaymentMethodAction = savedPaymentMethodAction,
+                    onViewMorePaymentMethods = onViewMorePaymentMethods,
+                    onManageOneSavedPaymentMethod = { onManageOneSavedPaymentMethod(displayedSavedPaymentMethod) },
+                )
+            },
+            onClick = { onSelectSavedPaymentMethod(displayedSavedPaymentMethod) },
+            rowStyle = rowStyle
+        )
+
+        if (paymentMethods.isNotEmpty()) OptionalEmbeddedDivider(rowStyle)
+    }
+}
+
+@OptIn(ExperimentalEmbeddedPaymentElementApi::class)
+@Composable
+internal fun EmbeddedNewPaymentMethodRowButtonsLayoutUi(
+    paymentMethods: List<DisplayablePaymentMethod>,
+    selection: PaymentMethodVerticalLayoutInteractor.Selection?,
+    isEnabled: Boolean,
+    imageLoader: StripeImageLoader,
+    rowStyle: Embedded.RowStyle,
+) {
+    val selectedIndex = remember(selection, paymentMethods) {
+        if (selection is PaymentMethodVerticalLayoutInteractor.Selection.New) {
+            val code = selection.code
+            paymentMethods.indexOfFirst { it.code == code }
+        } else {
+            -1
+        }
+    }
+
+    val isNewPaymentSelectedCard = (selection as? PaymentMethodVerticalLayoutInteractor.Selection.New)?.code ==
+        PaymentMethod.Type.Card.code
+
+    val isNewPaymentSelectedUSBankAccount =
+        (selection as? PaymentMethodVerticalLayoutInteractor.Selection.New)?.code ==
+            PaymentMethod.Type.USBankAccount.code
+
+    paymentMethods.forEachIndexed { index, item ->
+        val isSelected = index == selectedIndex
+
+        if (isSelected && selection is PaymentMethodVerticalLayoutInteractor.Selection.New && selection.canBeChanged) {
+            val displayablePaymentMethod = item.copy(
+                subtitle = selection.changeDetails?.resolvableString
+            )
+            NewPaymentMethodRowButton(
+                isEnabled = isEnabled,
+                isSelected = true,
+                displayablePaymentMethod = displayablePaymentMethod,
+                imageLoader = imageLoader,
+                rowStyle = rowStyle,
+                trailingContent = if (isNewPaymentSelectedCard || isNewPaymentSelectedUSBankAccount) {
+                    {
+                        EmbeddedNewPaymentMethodTrailingContent(
+                            showChevron = rowStyle !is Embedded.RowStyle.FlatWithCheckmark,
+                        )
+                    }
+                } else {
+                    null
+                }
+            )
+        } else {
+            NewPaymentMethodRowButton(
+                isEnabled = isEnabled,
+                isSelected = isSelected,
+                displayablePaymentMethod = item,
+                imageLoader = imageLoader,
+                rowStyle = rowStyle,
+            )
+        }
+
+        if (index != paymentMethods.lastIndex) OptionalEmbeddedDivider(rowStyle)
+    }
+}
+
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+const val TEST_TAG_CHANGE = "TEST_TAG_CHANGE"
+
+@Composable
+internal fun EmbeddedNewPaymentMethodTrailingContent(
+    showChevron: Boolean,
+) {
+    Row(
+        verticalAlignment = Alignment.CenterVertically,
+        modifier = Modifier
+            .testTag(TEST_TAG_CHANGE)
+            .padding(vertical = 4.dp)
+            .wrapContentHeight()
+    ) {
+        Text(
+            stringResource(id = com.stripe.android.uicore.R.string.stripe_change),
+            color = MaterialTheme.colors.primary,
+            style = MaterialTheme.typography.subtitle1,
+            fontWeight = FontWeight.Medium,
+        )
+        if (showChevron) {
+            Icon(
+                painter = painterResource(R.drawable.stripe_ic_chevron_right),
+                contentDescription = null,
+                tint = MaterialTheme.colors.primary,
+                modifier = Modifier.padding(start = 4.dp, top = 2.dp)
+            )
+        }
+    }
+}
diff --git a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/verticalmode/NewPaymentMethodRowButtonScreenshotTest.kt b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/verticalmode/NewPaymentMethodRowButtonScreenshotTest.kt
index ddac2d1e6ab..01c4e2f083e 100644
--- a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/verticalmode/NewPaymentMethodRowButtonScreenshotTest.kt
+++ b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/verticalmode/NewPaymentMethodRowButtonScreenshotTest.kt
@@ -4,6 +4,7 @@ import androidx.compose.foundation.layout.padding
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.unit.dp
 import com.stripe.android.paymentelement.ExperimentalEmbeddedPaymentElementApi
+import com.stripe.android.paymentsheet.PaymentSheet
 import com.stripe.android.screenshottesting.FontSize
 import com.stripe.android.screenshottesting.PaparazziRule
 import com.stripe.android.screenshottesting.SystemAppearance
@@ -138,4 +139,29 @@ internal class NewPaymentMethodRowButtonScreenshotTest {
             )
         }
     }
+
+    @Test
+    fun testEmbeddedNewSelected() {
+        paparazziRule.snapshot {
+            NewPaymentMethodRowButton(
+                isEnabled = true,
+                isSelected = true,
+                iconRes = R.drawable.stripe_ic_paymentsheet_pm_card,
+                iconUrl = null,
+                imageLoader = Mockito.mock(),
+                title = "Card",
+                subtitle = "Mastercard **** 4444",
+                iconRequiresTinting = false,
+                promoText = null,
+                onClick = {},
+                modifier = Modifier,
+                rowStyle = PaymentSheet.Appearance.Embedded.RowStyle.FlatWithRadio.default,
+                trailingContent = {
+                    EmbeddedNewPaymentMethodTrailingContent(
+                        showChevron = true
+                    )
+                }
+            )
+        }
+    }
 }
diff --git a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/verticalmode/PaymentMethodEmbeddedLayoutUIScreenshotTest.kt b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/verticalmode/PaymentMethodEmbeddedLayoutUIScreenshotTest.kt
index 6e8e8f9eb09..47d08ac3244 100644
--- a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/verticalmode/PaymentMethodEmbeddedLayoutUIScreenshotTest.kt
+++ b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/verticalmode/PaymentMethodEmbeddedLayoutUIScreenshotTest.kt
@@ -47,7 +47,7 @@ class PaymentMethodEmbeddedLayoutUIScreenshotTest {
     }
 
     @Test
-    fun testFlatWitRadio() {
+    fun testFlatWithRadio() {
         paparazziRule.snapshot {
             TestPaymentMethodLayoutUi(
                 rowStyle = getRowStyle(FlatWithRadio::class)
@@ -135,6 +135,20 @@ class PaymentMethodEmbeddedLayoutUIScreenshotTest {
         }
     }
 
+    @Test
+    fun testNewPaymentMethodsWithSelection() {
+        paparazziRule.snapshot {
+            TestPaymentMethodLayoutUiWithNewSelection(
+                rowStyle = FlatWithCheckmark.default,
+                selection = PaymentMethodVerticalLayoutInteractor.Selection.New(
+                    code = "card",
+                    changeDetails = "Visa **** 4242",
+                    canBeChanged = true
+                )
+            )
+        }
+    }
+
     @Composable
     private fun TestPaymentMethodLayoutUi(
         rowStyle: Embedded.RowStyle,
@@ -158,6 +172,29 @@ class PaymentMethodEmbeddedLayoutUIScreenshotTest {
         )
     }
 
+    @Composable
+    private fun TestPaymentMethodLayoutUiWithNewSelection(
+        rowStyle: Embedded.RowStyle,
+        selection: PaymentMethodVerticalLayoutInteractor.Selection? = null,
+        newPaymentMethods: List<DisplayablePaymentMethod> = paymentMethods
+    ) {
+        val scrollState = rememberScrollState()
+        PaymentMethodEmbeddedLayoutUI(
+            paymentMethods = newPaymentMethods,
+            displayedSavedPaymentMethod = null,
+            savedPaymentMethodAction =
+            PaymentMethodVerticalLayoutInteractor.SavedPaymentMethodAction.MANAGE_ALL,
+            selection = selection,
+            isEnabled = true,
+            onViewMorePaymentMethods = {},
+            onSelectSavedPaymentMethod = {},
+            onManageOneSavedPaymentMethod = {},
+            imageLoader = mock(),
+            rowStyle = rowStyle,
+            modifier = Modifier.verticalScroll(scrollState),
+        )
+    }
+
     @Suppress("CyclomaticComplexMethod")
     @OptIn(ExperimentalEmbeddedPaymentElementApi::class)
     private fun <T : Embedded.RowStyle> getRowStyle(
diff --git a/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.verticalmode_NewPaymentMethodRowButtonScreenshotTest_testEmbeddedNewSelected[DarkTheme,CrazyAppearance,DefaultFont].png b/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.verticalmode_NewPaymentMethodRowButtonScreenshotTest_testEmbeddedNewSelected[DarkTheme,CrazyAppearance,DefaultFont].png
new file mode 100644
index 00000000000..4af09c75d6e
Binary files /dev/null and b/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.verticalmode_NewPaymentMethodRowButtonScreenshotTest_testEmbeddedNewSelected[DarkTheme,CrazyAppearance,DefaultFont].png differ
diff --git a/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.verticalmode_NewPaymentMethodRowButtonScreenshotTest_testEmbeddedNewSelected[DarkTheme,CrazyAppearance,LargeFont].png b/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.verticalmode_NewPaymentMethodRowButtonScreenshotTest_testEmbeddedNewSelected[DarkTheme,CrazyAppearance,LargeFont].png
new file mode 100644
index 00000000000..5fbdff3e0e4
Binary files /dev/null and b/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.verticalmode_NewPaymentMethodRowButtonScreenshotTest_testEmbeddedNewSelected[DarkTheme,CrazyAppearance,LargeFont].png differ
diff --git a/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.verticalmode_NewPaymentMethodRowButtonScreenshotTest_testEmbeddedNewSelected[DarkTheme,CustomAppearance,DefaultFont].png b/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.verticalmode_NewPaymentMethodRowButtonScreenshotTest_testEmbeddedNewSelected[DarkTheme,CustomAppearance,DefaultFont].png
new file mode 100644
index 00000000000..1583a7f5379
Binary files /dev/null and b/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.verticalmode_NewPaymentMethodRowButtonScreenshotTest_testEmbeddedNewSelected[DarkTheme,CustomAppearance,DefaultFont].png differ
diff --git a/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.verticalmode_NewPaymentMethodRowButtonScreenshotTest_testEmbeddedNewSelected[DarkTheme,CustomAppearance,LargeFont].png b/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.verticalmode_NewPaymentMethodRowButtonScreenshotTest_testEmbeddedNewSelected[DarkTheme,CustomAppearance,LargeFont].png
new file mode 100644
index 00000000000..f2ad216b380
Binary files /dev/null and b/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.verticalmode_NewPaymentMethodRowButtonScreenshotTest_testEmbeddedNewSelected[DarkTheme,CustomAppearance,LargeFont].png differ
diff --git a/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.verticalmode_NewPaymentMethodRowButtonScreenshotTest_testEmbeddedNewSelected[DarkTheme,DefaultAppearance,DefaultFont].png b/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.verticalmode_NewPaymentMethodRowButtonScreenshotTest_testEmbeddedNewSelected[DarkTheme,DefaultAppearance,DefaultFont].png
new file mode 100644
index 00000000000..aca54421c6f
Binary files /dev/null and b/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.verticalmode_NewPaymentMethodRowButtonScreenshotTest_testEmbeddedNewSelected[DarkTheme,DefaultAppearance,DefaultFont].png differ
diff --git a/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.verticalmode_NewPaymentMethodRowButtonScreenshotTest_testEmbeddedNewSelected[DarkTheme,DefaultAppearance,LargeFont].png b/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.verticalmode_NewPaymentMethodRowButtonScreenshotTest_testEmbeddedNewSelected[DarkTheme,DefaultAppearance,LargeFont].png
new file mode 100644
index 00000000000..946c67b768f
Binary files /dev/null and b/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.verticalmode_NewPaymentMethodRowButtonScreenshotTest_testEmbeddedNewSelected[DarkTheme,DefaultAppearance,LargeFont].png differ
diff --git a/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.verticalmode_NewPaymentMethodRowButtonScreenshotTest_testEmbeddedNewSelected[LightTheme,CrazyAppearance,DefaultFont].png b/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.verticalmode_NewPaymentMethodRowButtonScreenshotTest_testEmbeddedNewSelected[LightTheme,CrazyAppearance,DefaultFont].png
new file mode 100644
index 00000000000..61268b4653f
Binary files /dev/null and b/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.verticalmode_NewPaymentMethodRowButtonScreenshotTest_testEmbeddedNewSelected[LightTheme,CrazyAppearance,DefaultFont].png differ
diff --git a/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.verticalmode_NewPaymentMethodRowButtonScreenshotTest_testEmbeddedNewSelected[LightTheme,CrazyAppearance,LargeFont].png b/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.verticalmode_NewPaymentMethodRowButtonScreenshotTest_testEmbeddedNewSelected[LightTheme,CrazyAppearance,LargeFont].png
new file mode 100644
index 00000000000..6cfd0187708
Binary files /dev/null and b/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.verticalmode_NewPaymentMethodRowButtonScreenshotTest_testEmbeddedNewSelected[LightTheme,CrazyAppearance,LargeFont].png differ
diff --git a/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.verticalmode_NewPaymentMethodRowButtonScreenshotTest_testEmbeddedNewSelected[LightTheme,CustomAppearance,DefaultFont].png b/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.verticalmode_NewPaymentMethodRowButtonScreenshotTest_testEmbeddedNewSelected[LightTheme,CustomAppearance,DefaultFont].png
new file mode 100644
index 00000000000..a01a9214d73
Binary files /dev/null and b/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.verticalmode_NewPaymentMethodRowButtonScreenshotTest_testEmbeddedNewSelected[LightTheme,CustomAppearance,DefaultFont].png differ
diff --git a/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.verticalmode_NewPaymentMethodRowButtonScreenshotTest_testEmbeddedNewSelected[LightTheme,CustomAppearance,LargeFont].png b/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.verticalmode_NewPaymentMethodRowButtonScreenshotTest_testEmbeddedNewSelected[LightTheme,CustomAppearance,LargeFont].png
new file mode 100644
index 00000000000..3b985f0124b
Binary files /dev/null and b/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.verticalmode_NewPaymentMethodRowButtonScreenshotTest_testEmbeddedNewSelected[LightTheme,CustomAppearance,LargeFont].png differ
diff --git a/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.verticalmode_NewPaymentMethodRowButtonScreenshotTest_testEmbeddedNewSelected[LightTheme,DefaultAppearance,DefaultFont].png b/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.verticalmode_NewPaymentMethodRowButtonScreenshotTest_testEmbeddedNewSelected[LightTheme,DefaultAppearance,DefaultFont].png
new file mode 100644
index 00000000000..e8bc37ca780
Binary files /dev/null and b/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.verticalmode_NewPaymentMethodRowButtonScreenshotTest_testEmbeddedNewSelected[LightTheme,DefaultAppearance,DefaultFont].png differ
diff --git a/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.verticalmode_NewPaymentMethodRowButtonScreenshotTest_testEmbeddedNewSelected[LightTheme,DefaultAppearance,LargeFont].png b/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.verticalmode_NewPaymentMethodRowButtonScreenshotTest_testEmbeddedNewSelected[LightTheme,DefaultAppearance,LargeFont].png
new file mode 100644
index 00000000000..583c21facae
Binary files /dev/null and b/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.verticalmode_NewPaymentMethodRowButtonScreenshotTest_testEmbeddedNewSelected[LightTheme,DefaultAppearance,LargeFont].png differ
diff --git a/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.verticalmode_PaymentMethodEmbeddedLayoutUIScreenshotTest_testFlatWitRadio[CrazyAppearance].png b/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.verticalmode_PaymentMethodEmbeddedLayoutUIScreenshotTest_testFlatWitRadio[CrazyAppearance].png
deleted file mode 100644
index 35c2fa02517..00000000000
Binary files a/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.verticalmode_PaymentMethodEmbeddedLayoutUIScreenshotTest_testFlatWitRadio[CrazyAppearance].png and /dev/null differ
diff --git a/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.verticalmode_PaymentMethodEmbeddedLayoutUIScreenshotTest_testFlatWithRadio[CrazyAppearance].png b/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.verticalmode_PaymentMethodEmbeddedLayoutUIScreenshotTest_testFlatWithRadio[CrazyAppearance].png
new file mode 100644
index 00000000000..0e7e135185e
Binary files /dev/null and b/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.verticalmode_PaymentMethodEmbeddedLayoutUIScreenshotTest_testFlatWithRadio[CrazyAppearance].png differ
diff --git a/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.verticalmode_PaymentMethodEmbeddedLayoutUIScreenshotTest_testFlatWitRadio[CustomAppearance].png b/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.verticalmode_PaymentMethodEmbeddedLayoutUIScreenshotTest_testFlatWithRadio[CustomAppearance].png
similarity index 100%
rename from paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.verticalmode_PaymentMethodEmbeddedLayoutUIScreenshotTest_testFlatWitRadio[CustomAppearance].png
rename to paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.verticalmode_PaymentMethodEmbeddedLayoutUIScreenshotTest_testFlatWithRadio[CustomAppearance].png
diff --git a/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.verticalmode_PaymentMethodEmbeddedLayoutUIScreenshotTest_testFlatWitRadio[DefaultAppearance].png b/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.verticalmode_PaymentMethodEmbeddedLayoutUIScreenshotTest_testFlatWithRadio[DefaultAppearance].png
similarity index 100%
rename from paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.verticalmode_PaymentMethodEmbeddedLayoutUIScreenshotTest_testFlatWitRadio[DefaultAppearance].png
rename to paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.verticalmode_PaymentMethodEmbeddedLayoutUIScreenshotTest_testFlatWithRadio[DefaultAppearance].png
diff --git a/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.verticalmode_PaymentMethodEmbeddedLayoutUIScreenshotTest_testNewPaymentMethodsWithSelection[CrazyAppearance].png b/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.verticalmode_PaymentMethodEmbeddedLayoutUIScreenshotTest_testNewPaymentMethodsWithSelection[CrazyAppearance].png
new file mode 100644
index 00000000000..0c7b83c5c45
Binary files /dev/null and b/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.verticalmode_PaymentMethodEmbeddedLayoutUIScreenshotTest_testNewPaymentMethodsWithSelection[CrazyAppearance].png differ
diff --git a/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.verticalmode_PaymentMethodEmbeddedLayoutUIScreenshotTest_testNewPaymentMethodsWithSelection[CustomAppearance].png b/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.verticalmode_PaymentMethodEmbeddedLayoutUIScreenshotTest_testNewPaymentMethodsWithSelection[CustomAppearance].png
new file mode 100644
index 00000000000..16130639fc7
Binary files /dev/null and b/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.verticalmode_PaymentMethodEmbeddedLayoutUIScreenshotTest_testNewPaymentMethodsWithSelection[CustomAppearance].png differ
diff --git a/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.verticalmode_PaymentMethodEmbeddedLayoutUIScreenshotTest_testNewPaymentMethodsWithSelection[DefaultAppearance].png b/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.verticalmode_PaymentMethodEmbeddedLayoutUIScreenshotTest_testNewPaymentMethodsWithSelection[DefaultAppearance].png
new file mode 100644
index 00000000000..8ad06c16fe4
Binary files /dev/null and b/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.verticalmode_PaymentMethodEmbeddedLayoutUIScreenshotTest_testNewPaymentMethodsWithSelection[DefaultAppearance].png differ


Reviews:
user: login: jaynewstrom-stripe
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-22T12:05:48Z
user: login: jaynewstrom-stripe
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-22T12:06:44Z
user: login: tianzhao-stripe
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-22T17:03:19Z
user: login: tianzhao-stripe
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-22T17:13:48Z
user: login: tjclawson-stripe
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-22T20:31:02Z
user: login: tjclawson-stripe
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-22T20:35:26Z
user: login: tianzhao-stripe
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-22T20:55:41Z
user: login: tianzhao-stripe
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-22T21:10:07Z
user: login: tjclawson-stripe
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-23T19:08:24Z

Review comments:
diff_hunk: @@ -233,3 +227,156 @@ private fun Embedded.RowStyle.endSeparatorInset(): Dp {
         is Embedded.RowStyle.FlatWithCheckmark -> endSeparatorInsetDp.dp
     }
 }
+
+@OptIn(ExperimentalEmbeddedPaymentElementApi::class)
+@Composable
+internal fun EmbeddedSavedPaymentMethodRowButton(
+    paymentMethods: List<DisplayablePaymentMethod>,
+    displayedSavedPaymentMethod: DisplayableSavedPaymentMethod?,
+    savedPaymentMethodAction: PaymentMethodVerticalLayoutInteractor.SavedPaymentMethodAction,
+    selection: PaymentMethodVerticalLayoutInteractor.Selection?,
+    isEnabled: Boolean,
+    onViewMorePaymentMethods: () -> Unit,
+    onManageOneSavedPaymentMethod: (DisplayableSavedPaymentMethod) -> Unit,
+    onSelectSavedPaymentMethod: (DisplayableSavedPaymentMethod) -> Unit,
+    rowStyle: Embedded.RowStyle,
+) {
+    if (displayedSavedPaymentMethod != null) {
+        SavedPaymentMethodRowButton(
+            displayableSavedPaymentMethod = displayedSavedPaymentMethod,
+            isEnabled = isEnabled,
+            isSelected = selection?.isSaved == true,
+            trailingContent = {
+                EmbeddedSavedPaymentMethodTrailingContent(
+                    savedPaymentMethodAction = savedPaymentMethodAction,
+                    rowStyle = rowStyle,
+                    onViewMorePaymentMethods = onViewMorePaymentMethods,
+                    onManageOneSavedPaymentMethod = { onManageOneSavedPaymentMethod(displayedSavedPaymentMethod) },
+                )
+            },
+            onClick = { onSelectSavedPaymentMethod(displayedSavedPaymentMethod) },
+            rowStyle = rowStyle
+        )
+
+        if (paymentMethods.isNotEmpty()) OptionalEmbeddedDivider(rowStyle)
+    }
+}
+
+@OptIn(ExperimentalEmbeddedPaymentElementApi::class)
+@Composable
+internal fun EmbeddedSavedPaymentMethodTrailingContent(
+    savedPaymentMethodAction: PaymentMethodVerticalLayoutInteractor.SavedPaymentMethodAction,
+    rowStyle: Embedded.RowStyle,
+    onViewMorePaymentMethods: () -> Unit,
+    onManageOneSavedPaymentMethod: () -> Unit,
+) {
+    when (savedPaymentMethodAction) {
+        PaymentMethodVerticalLayoutInteractor.SavedPaymentMethodAction.NONE -> Unit
+        PaymentMethodVerticalLayoutInteractor.SavedPaymentMethodAction.MANAGE_ONE -> {
+            EditButton(onClick = onManageOneSavedPaymentMethod)
+        }
+        PaymentMethodVerticalLayoutInteractor.SavedPaymentMethodAction.MANAGE_ALL -> {
+            ViewMoreButton(
+                showChevron = rowStyle !is Embedded.RowStyle.FlatWithCheckmark,
+                onViewMorePaymentMethods = onViewMorePaymentMethods
+            )
+        }
+    }
+}
+
+@OptIn(ExperimentalEmbeddedPaymentElementApi::class)
+@Composable
+internal fun EmbeddedNewPaymentMethodRowButtonsLayoutUi(
+    paymentMethods: List<DisplayablePaymentMethod>,
+    selection: PaymentMethodVerticalLayoutInteractor.Selection?,
+    isEnabled: Boolean,
+    imageLoader: StripeImageLoader,
+    rowStyle: Embedded.RowStyle,
+) {
+    val selectedIndex = remember(selection, paymentMethods) {
+        if (selection is PaymentMethodVerticalLayoutInteractor.Selection.New) {
+            val code = selection.code
+            paymentMethods.indexOfFirst { it.code == code }
+        } else {
+            -1
+        }
+    }
+
+    val isNewPaymentSelectedCard = (selection as? PaymentMethodVerticalLayoutInteractor.Selection.New)?.code ==
+        PaymentMethod.Type.Card.code
+
+    val isNewPaymentSelectedUSBankAccount =
+        (selection as? PaymentMethodVerticalLayoutInteractor.Selection.New)?.code ==
+            PaymentMethod.Type.USBankAccount.code
+
+    paymentMethods.forEachIndexed { index, item ->
+        val isSelected = index == selectedIndex
+
+        if (isSelected && selection is PaymentMethodVerticalLayoutInteractor.Selection.New && selection.canBeChanged) {
+            NewPaymentMethodRowButton(
+                isEnabled = isEnabled,
+                isSelected = true,
+                displayablePaymentMethod = item.copy(
+                    subtitle = selection.changeDetails?.resolvableString
+                ),
+                imageLoader = imageLoader,
+                rowStyle = rowStyle,
+                trailingContent = if (isNewPaymentSelectedCard || isNewPaymentSelectedUSBankAccount) {
+                    {
+                        EmbeddedNewPaymentMethodTrailingContent(
+                            showChevron = rowStyle !is Embedded.RowStyle.FlatWithCheckmark,
+                            onChangeClicked = {
+                                // TODO
path: paymentsheet/src/main/java/com/stripe/android/paymentsheet/verticalmode/PaymentMethodEmbeddedLayoutUI.kt
user: login: jaynewstrom-stripe
body: This should just do the same thing as the normal onClick. 
created_at: 2025-04-22T12:05:47Z
author_association: COLLABORATOR
path: paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.verticalmode_PaymentMethodEmbeddedLayoutUIScreenshotTest_testBottomSeparatorOnly[CrazyAppearance].png
user: login: jaynewstrom-stripe
body: Looks like we lost our view more chevrons. 
created_at: 2025-04-22T12:06:44Z
author_association: COLLABORATOR
path: paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.verticalmode_PaymentMethodEmbeddedLayoutUIScreenshotTest_testBottomSeparatorOnly[CrazyAppearance].png
user: login: tianzhao-stripe
body: based on the figma, we shouldn't see them in the checkmark style

https://www.figma.com/design/8a6ufbZeZdlsfr7KQqlh4Y/Embedded-MPE?node-id=1673-117732&t=Ek1PdtWxnXigKylb-4
created_at: 2025-04-22T17:03:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -233,3 +227,156 @@ private fun Embedded.RowStyle.endSeparatorInset(): Dp {
         is Embedded.RowStyle.FlatWithCheckmark -> endSeparatorInsetDp.dp
     }
 }
+
+@OptIn(ExperimentalEmbeddedPaymentElementApi::class)
+@Composable
+internal fun EmbeddedSavedPaymentMethodRowButton(
+    paymentMethods: List<DisplayablePaymentMethod>,
+    displayedSavedPaymentMethod: DisplayableSavedPaymentMethod?,
+    savedPaymentMethodAction: PaymentMethodVerticalLayoutInteractor.SavedPaymentMethodAction,
+    selection: PaymentMethodVerticalLayoutInteractor.Selection?,
+    isEnabled: Boolean,
+    onViewMorePaymentMethods: () -> Unit,
+    onManageOneSavedPaymentMethod: (DisplayableSavedPaymentMethod) -> Unit,
+    onSelectSavedPaymentMethod: (DisplayableSavedPaymentMethod) -> Unit,
+    rowStyle: Embedded.RowStyle,
+) {
+    if (displayedSavedPaymentMethod != null) {
+        SavedPaymentMethodRowButton(
+            displayableSavedPaymentMethod = displayedSavedPaymentMethod,
+            isEnabled = isEnabled,
+            isSelected = selection?.isSaved == true,
+            trailingContent = {
+                EmbeddedSavedPaymentMethodTrailingContent(
+                    savedPaymentMethodAction = savedPaymentMethodAction,
+                    rowStyle = rowStyle,
+                    onViewMorePaymentMethods = onViewMorePaymentMethods,
+                    onManageOneSavedPaymentMethod = { onManageOneSavedPaymentMethod(displayedSavedPaymentMethod) },
+                )
+            },
+            onClick = { onSelectSavedPaymentMethod(displayedSavedPaymentMethod) },
+            rowStyle = rowStyle
+        )
+
+        if (paymentMethods.isNotEmpty()) OptionalEmbeddedDivider(rowStyle)
+    }
+}
+
+@OptIn(ExperimentalEmbeddedPaymentElementApi::class)
+@Composable
+internal fun EmbeddedSavedPaymentMethodTrailingContent(
+    savedPaymentMethodAction: PaymentMethodVerticalLayoutInteractor.SavedPaymentMethodAction,
+    rowStyle: Embedded.RowStyle,
+    onViewMorePaymentMethods: () -> Unit,
+    onManageOneSavedPaymentMethod: () -> Unit,
+) {
+    when (savedPaymentMethodAction) {
+        PaymentMethodVerticalLayoutInteractor.SavedPaymentMethodAction.NONE -> Unit
+        PaymentMethodVerticalLayoutInteractor.SavedPaymentMethodAction.MANAGE_ONE -> {
+            EditButton(onClick = onManageOneSavedPaymentMethod)
+        }
+        PaymentMethodVerticalLayoutInteractor.SavedPaymentMethodAction.MANAGE_ALL -> {
+            ViewMoreButton(
+                showChevron = rowStyle !is Embedded.RowStyle.FlatWithCheckmark,
+                onViewMorePaymentMethods = onViewMorePaymentMethods
+            )
+        }
+    }
+}
+
+@OptIn(ExperimentalEmbeddedPaymentElementApi::class)
+@Composable
+internal fun EmbeddedNewPaymentMethodRowButtonsLayoutUi(
+    paymentMethods: List<DisplayablePaymentMethod>,
+    selection: PaymentMethodVerticalLayoutInteractor.Selection?,
+    isEnabled: Boolean,
+    imageLoader: StripeImageLoader,
+    rowStyle: Embedded.RowStyle,
+) {
+    val selectedIndex = remember(selection, paymentMethods) {
+        if (selection is PaymentMethodVerticalLayoutInteractor.Selection.New) {
+            val code = selection.code
+            paymentMethods.indexOfFirst { it.code == code }
+        } else {
+            -1
+        }
+    }
+
+    val isNewPaymentSelectedCard = (selection as? PaymentMethodVerticalLayoutInteractor.Selection.New)?.code ==
+        PaymentMethod.Type.Card.code
+
+    val isNewPaymentSelectedUSBankAccount =
+        (selection as? PaymentMethodVerticalLayoutInteractor.Selection.New)?.code ==
+            PaymentMethod.Type.USBankAccount.code
+
+    paymentMethods.forEachIndexed { index, item ->
+        val isSelected = index == selectedIndex
+
+        if (isSelected && selection is PaymentMethodVerticalLayoutInteractor.Selection.New && selection.canBeChanged) {
+            NewPaymentMethodRowButton(
+                isEnabled = isEnabled,
+                isSelected = true,
+                displayablePaymentMethod = item.copy(
+                    subtitle = selection.changeDetails?.resolvableString
+                ),
+                imageLoader = imageLoader,
+                rowStyle = rowStyle,
+                trailingContent = if (isNewPaymentSelectedCard || isNewPaymentSelectedUSBankAccount) {
+                    {
+                        EmbeddedNewPaymentMethodTrailingContent(
+                            showChevron = rowStyle !is Embedded.RowStyle.FlatWithCheckmark,
+                            onChangeClicked = {
+                                // TODO
path: paymentsheet/src/main/java/com/stripe/android/paymentsheet/verticalmode/PaymentMethodEmbeddedLayoutUI.kt
user: login: tianzhao-stripe
body: Gonna remove the clickable modifier to the change text if its going to do the same thing as the normal onClick
created_at: 2025-04-22T17:13:48Z
author_association: CONTRIBUTOR
path: paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.verticalmode_PaymentMethodEmbeddedLayoutUIScreenshotTest_testBottomSeparatorOnly[CrazyAppearance].png
user: login: tjclawson-stripe
body: Can you verify that with design? It wasn't flagged in Quality Review
created_at: 2025-04-22T20:31:02Z
author_association: COLLABORATOR
diff_hunk: @@ -233,3 +226,152 @@ private fun Embedded.RowStyle.endSeparatorInset(): Dp {
         is Embedded.RowStyle.FlatWithCheckmark -> endSeparatorInsetDp.dp
     }
 }
+
+@OptIn(ExperimentalEmbeddedPaymentElementApi::class)
+@Composable
+internal fun EmbeddedSavedPaymentMethodRowButton(
+    paymentMethods: List<DisplayablePaymentMethod>,
+    displayedSavedPaymentMethod: DisplayableSavedPaymentMethod?,
+    savedPaymentMethodAction: PaymentMethodVerticalLayoutInteractor.SavedPaymentMethodAction,
+    selection: PaymentMethodVerticalLayoutInteractor.Selection?,
+    isEnabled: Boolean,
+    onViewMorePaymentMethods: () -> Unit,
+    onManageOneSavedPaymentMethod: (DisplayableSavedPaymentMethod) -> Unit,
+    onSelectSavedPaymentMethod: (DisplayableSavedPaymentMethod) -> Unit,
+    rowStyle: Embedded.RowStyle,
+) {
+    if (displayedSavedPaymentMethod != null) {
+        SavedPaymentMethodRowButton(
+            displayableSavedPaymentMethod = displayedSavedPaymentMethod,
+            isEnabled = isEnabled,
+            isSelected = selection?.isSaved == true,
+            trailingContent = {
+                EmbeddedSavedPaymentMethodTrailingContent(
+                    savedPaymentMethodAction = savedPaymentMethodAction,
+                    rowStyle = rowStyle,
+                    onViewMorePaymentMethods = onViewMorePaymentMethods,
+                    onManageOneSavedPaymentMethod = { onManageOneSavedPaymentMethod(displayedSavedPaymentMethod) },
+                )
+            },
+            onClick = { onSelectSavedPaymentMethod(displayedSavedPaymentMethod) },
+            rowStyle = rowStyle
+        )
+
+        if (paymentMethods.isNotEmpty()) OptionalEmbeddedDivider(rowStyle)
+    }
+}
+
+@OptIn(ExperimentalEmbeddedPaymentElementApi::class)
+@Composable
+internal fun EmbeddedSavedPaymentMethodTrailingContent(
path: paymentsheet/src/main/java/com/stripe/android/paymentsheet/verticalmode/PaymentMethodEmbeddedLayoutUI.kt
user: login: tjclawson-stripe
body: This is basically a duplicate of `SavedPaymentMethodTrailingContent` aside from the `showChevron` logic. Is there a good reason for it to be separate or can you add a `showChevron` param to the existing composable?
created_at: 2025-04-22T20:35:26Z
author_association: COLLABORATOR
diff_hunk: @@ -233,3 +226,152 @@ private fun Embedded.RowStyle.endSeparatorInset(): Dp {
         is Embedded.RowStyle.FlatWithCheckmark -> endSeparatorInsetDp.dp
     }
 }
+
+@OptIn(ExperimentalEmbeddedPaymentElementApi::class)
+@Composable
+internal fun EmbeddedSavedPaymentMethodRowButton(
+    paymentMethods: List<DisplayablePaymentMethod>,
+    displayedSavedPaymentMethod: DisplayableSavedPaymentMethod?,
+    savedPaymentMethodAction: PaymentMethodVerticalLayoutInteractor.SavedPaymentMethodAction,
+    selection: PaymentMethodVerticalLayoutInteractor.Selection?,
+    isEnabled: Boolean,
+    onViewMorePaymentMethods: () -> Unit,
+    onManageOneSavedPaymentMethod: (DisplayableSavedPaymentMethod) -> Unit,
+    onSelectSavedPaymentMethod: (DisplayableSavedPaymentMethod) -> Unit,
+    rowStyle: Embedded.RowStyle,
+) {
+    if (displayedSavedPaymentMethod != null) {
+        SavedPaymentMethodRowButton(
+            displayableSavedPaymentMethod = displayedSavedPaymentMethod,
+            isEnabled = isEnabled,
+            isSelected = selection?.isSaved == true,
+            trailingContent = {
+                EmbeddedSavedPaymentMethodTrailingContent(
+                    savedPaymentMethodAction = savedPaymentMethodAction,
+                    rowStyle = rowStyle,
+                    onViewMorePaymentMethods = onViewMorePaymentMethods,
+                    onManageOneSavedPaymentMethod = { onManageOneSavedPaymentMethod(displayedSavedPaymentMethod) },
+                )
+            },
+            onClick = { onSelectSavedPaymentMethod(displayedSavedPaymentMethod) },
+            rowStyle = rowStyle
+        )
+
+        if (paymentMethods.isNotEmpty()) OptionalEmbeddedDivider(rowStyle)
+    }
+}
+
+@OptIn(ExperimentalEmbeddedPaymentElementApi::class)
+@Composable
+internal fun EmbeddedSavedPaymentMethodTrailingContent(
path: paymentsheet/src/main/java/com/stripe/android/paymentsheet/verticalmode/PaymentMethodEmbeddedLayoutUI.kt
user: login: tianzhao-stripe
body: I'll use SavedPaymentMethodTrailingContent
created_at: 2025-04-22T20:55:41Z
author_association: CONTRIBUTOR
path: paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.verticalmode_PaymentMethodEmbeddedLayoutUIScreenshotTest_testBottomSeparatorOnly[CrazyAppearance].png
user: login: tianzhao-stripe
body: to unblock the rest of the PR I'm not going to include it in this PR.
created_at: 2025-04-22T21:10:07Z
author_association: CONTRIBUTOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-04-22T00:59:12Z
body: Diffuse output:


```
OLD: identity-example-release-base.apk (signature: V1, V2)
NEW: identity-example-release-pr.apk (signature: V1, V2)

          │          compressed          │         uncompressed         
          ├───────────┬───────────┬──────┼───────────┬───────────┬──────
 APK      │ old       │ new       │ diff │ old       │ new       │ diff 
──────────┼───────────┼───────────┼──────┼───────────┼───────────┼──────
      dex │   2.1 MiB │   2.1 MiB │  0 B │   4.3 MiB │   4.3 MiB │  0 B 
     arsc │     1 MiB │     1 MiB │  0 B │     1 MiB │     1 MiB │  0 B 
 manifest │   2.3 KiB │   2.3 KiB │  0 B │     8 KiB │     8 KiB │  0 B 
      res │ 302.9 KiB │ 302.9 KiB │  0 B │   457 KiB │   457 KiB │  0 B 
   native │   6.2 MiB │   6.2 MiB │  0 B │  15.8 MiB │  15.8 MiB │  0 B 
    asset │   7.7 KiB │   7.7 KiB │  0 B │   7.4 KiB │   7.4 KiB │  0 B 
    other │  95.7 KiB │  95.7 KiB │ -5 B │ 183.5 KiB │ 183.5 KiB │  0 B 
──────────┼───────────┼───────────┼──────┼───────────┼───────────┼──────
    total │   9.8 MiB │   9.8 MiB │ -5 B │  21.8 MiB │  21.8 MiB │  0 B 

 DEX     │ old   │ new   │ diff      
─────────┼───────┼───────┼───────────
   files │     1 │     1 │ 0         
 strings │ 20668 │ 20668 │ 0 (+0 -0) 
   types │  6493 │  6493 │ 0 (+0 -0) 
 classes │  5259 │  5259 │ 0 (+0 -0) 
 methods │ 31489 │ 31489 │ 0 (+0 -0) 
  fields │ 18220 │ 18220 │ 0 (+0 -0) 

 ARSC    │ old  │ new  │ diff 
─────────┼──────┼──────┼──────
 configs │  164 │  164 │  0   
 entries │ 3646 │ 3646 │  0
```

<details>
  <summary>APK</summary>
  
```
   compressed    │   uncompressed   │                        
──────────┬──────┼───────────┬──────┤                        
 size     │ diff │ size      │ diff │ path                   
──────────┼──────┼───────────┼──────┼────────────────────────
 25.9 KiB │ -5 B │  64.5 KiB │  0 B │ ∆ META-INF/MANIFEST.MF 
 29.2 KiB │ -1 B │  64.6 KiB │  0 B │ ∆ META-INF/CERT.SF     
  1.2 KiB │ +1 B │   1.2 KiB │  0 B │ ∆ META-INF/CERT.RSA    
──────────┼──────┼───────────┼──────┼────────────────────────
 56.2 KiB │ -5 B │ 130.4 KiB │  0 B │ (total)
```
</details>
author_association: CONTRIBUTOR
user: login: emerge-tools[bot]
created_at: 2025-04-22T21:12:42Z
body: ## 1 build increased size, 2 builds decreased size

| Name | Version | Download | Change | Install | Change | Approval |
| :--- | :---: | :---: | :---: | :---: | :---: | :---: |
| <a href="https://www.emergetools.com/build/005d92ef-ab40-4178-a14e-285d4537dd1d?utm_source=github&utm_medium=vcs" target="_blank" rel="noreferrer noopener">Stripe Identity Example</a><br>com.stripe.android.identity.example.theme1 | 21.11.1-theme1 (20) | 3.9 MB | ⬆️ 314 B | 9.2 MB | ⬇️ 1 B | N/A |
| <a href="https://www.emergetools.com/build/b81730e8-40d3-43e4-803e-7343b5ddaf34?utm_source=github&utm_medium=vcs" target="_blank" rel="noreferrer noopener">Financial Connections Example</a><br>com.stripe.android.financialconnections.example | 21.11.1 (211101) | 4.4 MB | ⬇️ 2.2 MB (-33.04%) | 9.7 MB | ⬇️ 3.5 MB (-26.32%) | N/A |
| <a href="https://www.emergetools.com/build/353bb595-6377-493e-b678-6326b8a5452c?utm_source=github&utm_medium=vcs" target="_blank" rel="noreferrer noopener">PaymentSheet Example</a><br>com.stripe.android.paymentsheet.example | 21.11.1 (11) | 7.3 MB | ⬇️ 2.2 MB (-22.91%) | 14.9 MB | ⬇️ 3.6 MB (-19.29%) | N/A |


## <a href="https://www.emergetools.com/build/005d92ef-ab40-4178-a14e-285d4537dd1d?utm_source=github&utm_medium=vcs" target="_blank" rel="noreferrer noopener">Stripe Identity Example 21.11.1-theme1 (20)</a><br>com.stripe.android.identity.example.theme1

<a href="https://www.emergetools.com/compare?head=005d92ef-ab40-4178-a14e-285d4537dd1d&base=e1aa682c-fdc8-476c-a212-23be5daec37b&sizeOpen=true&utm_source=github&utm_medium=vcs&utm_campaign=compare-size" target="_blank" rel="noreferrer noopener">⚖️ Compare build</a>
<a href="https://www.emergetools.com/compare?head=005d92ef-ab40-4178-a14e-285d4537dd1d&base=e1aa682c-fdc8-476c-a212-23be5daec37b&perfOpen=true&utm_source=github&utm_medium=vcs&utm_campaign=performance-compare" target="_blank" rel="noreferrer noopener">⏱️ Analyze build performance</a>

**Total install size change:** ⬇️ 1 B
**Total download size change:** ⬆️ 314 B

*Largest size changes*

| Item | Install Size Change | Download Size Change |
| :---: | :---: | :---: |
| Other | ⬆️ -1 B | ⬆️ 314 B |
## <a href="https://www.emergetools.com/build/b81730e8-40d3-43e4-803e-7343b5ddaf34?utm_source=github&utm_medium=vcs" target="_blank" rel="noreferrer noopener">Financial Connections Example 21.11.1 (211101)</a><br>com.stripe.android.financialconnections.example

<a href="https://www.emergetools.com/compare?head=b81730e8-40d3-43e4-803e-7343b5ddaf34&base=29b875b1-2b7a-464f-9af3-85c3237af3c1&sizeOpen=true&utm_source=github&utm_medium=vcs&utm_campaign=compare-size" target="_blank" rel="noreferrer noopener">⚖️ Compare build</a>
<a href="https://www.emergetools.com/compare?head=b81730e8-40d3-43e4-803e-7343b5ddaf34&base=29b875b1-2b7a-464f-9af3-85c3237af3c1&perfOpen=true&utm_source=github&utm_medium=vcs&utm_campaign=performance-compare" target="_blank" rel="noreferrer noopener">⏱️ Analyze build performance</a>

**Total install size change:** ⬇️ 3.5 MB (-26.32%)
**Total download size change:** ⬇️ 2.2 MB (-33.04%)

*Largest size changes*

| Item | Install Size Change | Download Size Change |
| :---: | :---: | :---: |
| 🗑 org | ⬇️ -1.2 MB | ⬇️ -1.2 MB |
| 🗑 org | ⬇️ -2.5 MB | ⬇️ -1.2 MB |
| 📝 com.stripe.android.financialconnections.features.common.SharedPar... | ⬆️ 90.5 kB | ⬆️ 42.5 kB |
| 🗑 com.stripe.android.paymentsheet.forms.PlaceholderHelper | ⬇️ -67.3 kB | ⬇️ -31.0 kB |
| 📝 com.stripe.android.financialconnections.features.common.LoadingCo... | ⬆️ 63.2 kB | ⬆️ 29.7 kB |

<details>
  <summary>View Treemap</summary>
  
  [![Image of diff](https://diff-treemap-images.s3.us-west-1.amazonaws.com/6272f78c33c477006f824149/29b875b1-2b7a-464f-9af3-85c3237af3c1.b81730e8-40d3-43e4-803e-7343b5ddaf34.png)](https://www.emergetools.com/compare?head=b81730e8-40d3-43e4-803e-7343b5ddaf34&base=29b875b1-2b7a-464f-9af3-85c3237af3c1&sizeOpen=true&utm_source=github&utm_medium=vcs)
  
</details>

## <a href="https://www.emergetools.com/build/353bb595-6377-493e-b678-6326b8a5452c?utm_source=github&utm_medium=vcs" target="_blank" rel="noreferrer noopener">PaymentSheet Example 21.11.1 (11)</a><br>com.stripe.android.paymentsheet.example

<a href="https://www.emergetools.com/compare?head=353bb595-6377-493e-b678-6326b8a5452c&base=62a4a3ec-c0bf-43cd-a9b3-194a4dbeaccd&sizeOpen=true&utm_source=github&utm_medium=vcs&utm_campaign=compare-size" target="_blank" rel="noreferrer noopener">⚖️ Compare build</a>
<a href="https://www.emergetools.com/compare?head=353bb595-6377-493e-b678-6326b8a5452c&base=62a4a3ec-c0bf-43cd-a9b3-194a4dbeaccd&perfOpen=true&utm_source=github&utm_medium=vcs&utm_campaign=performance-compare" target="_blank" rel="noreferrer noopener">⏱️ Analyze build performance</a>

**Total install size change:** ⬇️ 3.6 MB (-19.29%)
**Total download size change:** ⬇️ 2.2 MB (-22.91%)

*Largest size changes*

| Item | Install Size Change | Download Size Change |
| :---: | :---: | :---: |
| 🗑 org.bouncycastle | ⬇️ -3.3 MB | ⬇️ -1.3 MB |
| 🗑 org | ⬇️ -1.2 MB | ⬇️ -1.2 MB |
| 🗑 androidx.compose.material.icons | ⬇️ -272.9 kB | ⬇️ -124.8 kB |
| 📝 androidx.compose.ui.text.android.SpannedExtensions_androidKt | ⬆️ 55.0 kB | ⬆️ 24.8 kB |
| 📝 com.stripe.android.financialconnections.features.manualentry.Manu... | ⬆️ 47.2 kB | ⬆️ 21.3 kB |

<details>
  <summary>View Treemap</summary>
  
  [![Image of diff](https://diff-treemap-images.s3.us-west-1.amazonaws.com/6272f78c33c477006f824149/62a4a3ec-c0bf-43cd-a9b3-194a4dbeaccd.353bb595-6377-493e-b678-6326b8a5452c.png)](https://www.emergetools.com/compare?head=353bb595-6377-493e-b678-6326b8a5452c&base=62a4a3ec-c0bf-43cd-a9b3-194a4dbeaccd&sizeOpen=true&utm_source=github&utm_medium=vcs)
  
</details>


---

:flying_saucer: Powered by <a href="https://www.emergetools.com/?utm_source=github&utm_medium=vcs" target="_blank" rel="noreferrer noopener">Emerge Tools</a>

Comment trigger: Size diff threshold of 100.00kB exceeded
author_association: NONE