Skip to content

Commit

Permalink
Select payment method when it is set as the default PM (#10257)
Browse files Browse the repository at this point in the history
* Select payment method when it is set as the default PM

* Fix test
  • Loading branch information
amk-stripe authored Feb 24, 2025
1 parent 389a5b7 commit 7048c91
Show file tree
Hide file tree
Showing 9 changed files with 136 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -199,9 +199,7 @@ internal class DefaultEmbeddedContentHelper @Inject constructor(
uiContext = uiContext,
customerRepository = customerRepository,
selection = selectionHolder.selection,
clearSelection = {
setSelection(null)
},
setSelection = ::setSelection,
customerStateHolder = customerStateHolder,
prePaymentMethodRemoveActions = {},
postPaymentMethodRemoveActions = {},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,7 @@ internal class ManageSavedPaymentMethodMutatorFactory @Inject constructor(
uiContext = uiContext,
customerRepository = customerRepository,
selection = selectionHolder.selection,
clearSelection = {
selectionHolder.set(null)
},
setSelection = selectionHolder::set,
customerStateHolder = customerStateHolder,
prePaymentMethodRemoveActions = {
if (customerStateHolder.paymentMethods.value.size > 1) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ internal class CustomerStateHolder(
val newCustomer = customer.value?.copy(defaultPaymentMethodId = paymentMethod?.id)

savedStateHandle[SAVED_CUSTOMER] = newCustomer
updateMostRecentlySelectedSavedPaymentMethod(paymentMethod = paymentMethod)
}

fun updateMostRecentlySelectedSavedPaymentMethod(paymentMethod: PaymentMethod?) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ internal class SavedPaymentMethodMutator(
private val uiContext: CoroutineContext,
private val customerRepository: CustomerRepository,
private val selection: StateFlow<PaymentSelection?>,
private val clearSelection: () -> Unit,
private val setSelection: (PaymentSelection?) -> Unit,
private val customerStateHolder: CustomerStateHolder,
// Actions that should be taken after removing a payment method has succeeded but before we've fully updated our
// state to reflect that. For example, in our manage payment method screen, we want to navigate back to the
Expand Down Expand Up @@ -158,7 +158,7 @@ internal class SavedPaymentMethodMutator(
if (didRemoveSelectedItem) {
// Remove the current selection. The new selection will be set when we're computing
// the next PaymentOptionsState.
clearSelection()
setSelection(null)
}

return customerRepository.detachPaymentMethod(
Expand All @@ -184,7 +184,7 @@ internal class SavedPaymentMethodMutator(
)

if ((selection.value as? PaymentSelection.Saved)?.paymentMethod?.id == paymentMethodId) {
clearSelection()
setSelection(null)
}

withContext(uiContext) {
Expand Down Expand Up @@ -222,6 +222,7 @@ internal class SavedPaymentMethodMutator(
paymentMethodId = paymentMethod.id,
).onSuccess {
customerStateHolder.setDefaultPaymentMethod(paymentMethod = paymentMethod)
setSelection(PaymentSelection.Saved(paymentMethod = paymentMethod))
}.map {}
}

Expand Down Expand Up @@ -398,8 +399,8 @@ internal class SavedPaymentMethodMutator(
uiContext = Dispatchers.Main,
customerRepository = viewModel.customerRepository,
selection = viewModel.selection,
setSelection = viewModel::updateSelection,
customerStateHolder = viewModel.customerStateHolder,
clearSelection = { viewModel.updateSelection(null) },
prePaymentMethodRemoveActions = {
navigateBackOnPaymentMethodRemoved(viewModel)
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,19 @@ import androidx.compose.ui.test.hasAnyAncestor
import androidx.compose.ui.test.hasAnyDescendant
import androidx.compose.ui.test.hasTestTag
import androidx.compose.ui.test.hasText
import androidx.compose.ui.test.isEnabled
import androidx.compose.ui.test.junit4.ComposeTestRule
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.performClick
import androidx.test.core.app.ActivityScenario
import androidx.test.core.app.ApplicationProvider
import androidx.test.espresso.Espresso
import com.stripe.android.model.CardBrand
import com.stripe.android.model.PaymentMethod
import com.stripe.android.model.PaymentMethodFixtures
import com.stripe.android.networktesting.NetworkRule
import com.stripe.android.networktesting.RequestMatchers
import com.stripe.android.networktesting.testBodyFromFile
import com.stripe.android.paymentsheet.state.PaymentElementLoader
import com.stripe.android.paymentsheet.ui.PAYMENT_SHEET_EDIT_BUTTON_TEST_TAG
import com.stripe.android.paymentsheet.ui.SAVED_PAYMENT_OPTION_TAB_LAYOUT_TEST_TAG
Expand All @@ -43,6 +47,10 @@ internal class CustomerSessionPaymentSheetActivityTest {
private val composeTestRule = createAndroidComposeRule<PaymentSheetActivity>()
private val networkRule = NetworkRule()

private val verticalModePage = VerticalModePage(composeTestRule)
private val managePage = ManagePage(composeTestRule)
private val editPage = EditPage(composeTestRule)

@get:Rule
val ruleChain: RuleChain = RuleChain
.outerRule(composeTestRule)
Expand Down Expand Up @@ -292,11 +300,60 @@ internal class CustomerSessionPaymentSheetActivityTest {
composeTestRule.onUpdateScreenRemoveButton().assertDoesNotExist()
}

@Test
fun `Setting default card selects that card in vertical mode`() {
val cards = PaymentMethodFixtures.createCards(2)
runTest(
cards = cards,
setAsDefaultFeatureEnabled = true,
paymentMethodLayout = PaymentSheet.PaymentMethodLayout.Vertical,
) {
val originallySelectedPaymentMethodId = cards[0].id!!
val newDefaultPaymentMethodId = cards[1].id!!
verticalModePage.assertHasSavedPaymentMethods()
verticalModePage.assertHasSelectedSavedPaymentMethod(originallySelectedPaymentMethodId)
verticalModePage.assertPrimaryButton(isEnabled())

verticalModePage.clickViewMore()
managePage.waitUntilVisible()
verticalModePage.assertHasSelectedSavedPaymentMethod(originallySelectedPaymentMethodId)
managePage.clickEdit()
managePage.clickEdit(newDefaultPaymentMethodId)
setDefaultPaymentMethod()

managePage.waitUntilVisible()
managePage.clickDone()
verticalModePage.assertHasSelectedSavedPaymentMethod(newDefaultPaymentMethodId)
Espresso.pressBack()

verticalModePage.waitUntilVisible()
verticalModePage.assertHasSelectedSavedPaymentMethod(newDefaultPaymentMethodId)
verticalModePage.assertPrimaryButton(isEnabled())
}
}

private fun setDefaultPaymentMethod() {
editPage.waitUntilVisible()
editPage.clickSetAsDefaultCheckbox()

networkRule.enqueue(
RequestMatchers.host("api.stripe.com"),
RequestMatchers.method("POST"),
RequestMatchers.path("/v1/elements/customers/cus_12345/set_default_payment_method"),
) { response ->
response.testBodyFromFile("elements-sessions-customers-set-default-pm.json")
}

editPage.update()
}

private fun runTest(
cards: List<PaymentMethod>,
isPaymentMethodRemoveEnabled: Boolean,
canRemoveLastPaymentMethodConfig: Boolean,
canRemoveLastPaymentMethodServer: Boolean,
isPaymentMethodRemoveEnabled: Boolean = true,
canRemoveLastPaymentMethodConfig: Boolean = true,
canRemoveLastPaymentMethodServer: Boolean = true,
setAsDefaultFeatureEnabled: Boolean = false,
paymentMethodLayout: PaymentSheet.PaymentMethodLayout = PaymentSheet.PaymentMethodLayout.Horizontal,
test: (PaymentSheetActivity) -> Unit,
) {
networkRule.enqueue(
Expand All @@ -309,6 +366,7 @@ internal class CustomerSessionPaymentSheetActivityTest {
cards = cards,
isPaymentMethodRemoveEnabled = isPaymentMethodRemoveEnabled,
canRemoveLastPaymentMethod = canRemoveLastPaymentMethodServer,
setAsDefaultFeatureEnabled = setAsDefaultFeatureEnabled,
)
)
}
Expand All @@ -330,18 +388,20 @@ internal class CustomerSessionPaymentSheetActivityTest {
),
allowsRemovalOfLastSavedPaymentMethod = canRemoveLastPaymentMethodConfig,
preferredNetworks = listOf(CardBrand.CartesBancaires, CardBrand.Visa),
paymentMethodLayout = PaymentSheet.PaymentMethodLayout.Horizontal,
paymentMethodLayout = paymentMethodLayout,
),
statusBarColor = PaymentSheetFixtures.STATUS_BAR_COLOR,
)
)
).use { scenario ->
scenario.onActivity { activity ->
composeTestRule.waitUntil(timeoutMillis = 2_000) {
composeTestRule
.onAllNodes(hasTestTag(SAVED_PAYMENT_OPTION_TAB_LAYOUT_TEST_TAG))
.fetchSemanticsNodes()
.isNotEmpty()
if (paymentMethodLayout == PaymentSheet.PaymentMethodLayout.Horizontal) {
composeTestRule.waitUntil(timeoutMillis = 2_000) {
composeTestRule
.onAllNodes(hasTestTag(SAVED_PAYMENT_OPTION_TAB_LAYOUT_TEST_TAG))
.fetchSemanticsNodes()
.isNotEmpty()
}
}

test(activity)
Expand Down Expand Up @@ -390,6 +450,7 @@ internal class CustomerSessionPaymentSheetActivityTest {
cards: List<PaymentMethod>,
isPaymentMethodRemoveEnabled: Boolean,
canRemoveLastPaymentMethod: Boolean,
setAsDefaultFeatureEnabled: Boolean,
): String {
val cardsArray = JSONArray()

Expand All @@ -399,17 +460,9 @@ internal class CustomerSessionPaymentSheetActivityTest {

val cardsStringified = cardsArray.toString(2)

val isPaymentMethodRemoveStringified = if (isPaymentMethodRemoveEnabled) {
"enabled"
} else {
"disabled"
}

val canRemoveLastPaymentMethodStringified = if (canRemoveLastPaymentMethod) {
"enabled"
} else {
"disabled"
}
val isPaymentMethodRemoveStringified = isPaymentMethodRemoveEnabled.toFeatureState()
val canRemoveLastPaymentMethodStringified = canRemoveLastPaymentMethod.toFeatureState()
val setAsDefaultFeatureEnabledStringified = setAsDefaultFeatureEnabled.toFeatureState()

return """
{
Expand Down Expand Up @@ -442,7 +495,8 @@ internal class CustomerSessionPaymentSheetActivityTest {
"payment_method_save": "enabled",
"payment_method_remove": "$isPaymentMethodRemoveStringified",
"payment_method_remove_last": "$canRemoveLastPaymentMethodStringified",
"payment_method_save_allow_redisplay_override": null
"payment_method_save_allow_redisplay_override": null,
"payment_method_set_as_default": $setAsDefaultFeatureEnabledStringified
}
},
"customer_sheet": {
Expand Down Expand Up @@ -516,5 +570,13 @@ internal class CustomerSessionPaymentSheetActivityTest {
}
""".trimIndent()
}

private fun Boolean.toFeatureState(): String {
return if (this) {
"enabled"
} else {
"disabled"
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import com.stripe.android.paymentsheet.ui.REMOVE_BUTTON_LOADING
import com.stripe.android.paymentsheet.ui.UPDATE_PM_REMOVE_BUTTON_TEST_TAG
import com.stripe.android.paymentsheet.ui.UPDATE_PM_SAVE_BUTTON_TEST_TAG
import com.stripe.android.paymentsheet.ui.UPDATE_PM_SCREEN_TEST_TAG
import com.stripe.android.paymentsheet.ui.UPDATE_PM_SET_AS_DEFAULT_CHECKBOX_TEST_TAG
import com.stripe.android.ui.core.elements.TEST_TAG_DIALOG_CONFIRM_BUTTON
import com.stripe.android.uicore.elements.DROPDOWN_MENU_CLICKABLE_TEST_TAG
import com.stripe.android.uicore.elements.TEST_TAG_DROP_DOWN_CHOICE
Expand Down Expand Up @@ -110,4 +111,10 @@ internal class EditPage(
.isEmpty()
}
}

fun clickSetAsDefaultCheckbox() {
composeTestRule.onNodeWithTag(
UPDATE_PM_SET_AS_DEFAULT_CHECKBOX_TEST_TAG
).performClick()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,31 @@ class SavedPaymentMethodMutatorTest {
}
}

@Test
fun `setDefaultPaymentMethod updates selection on success`() {
val paymentMethods = PaymentMethodFixtures.createCards(3)

runScenario(
customerRepository = FakeCustomerRepository(
onSetDefaultPaymentMethod = { Result.success(mock()) }
)
) {
customerStateHolder.setCustomerState(
createCustomerState(
paymentMethods = paymentMethods,
defaultPaymentMethodId = paymentMethods.first().id,
)
)

val newDefaultPaymentMethod = paymentMethods[1]
savedPaymentMethodMutator.setDefaultPaymentMethod(newDefaultPaymentMethod)

selectionSource.test {
assertThat(awaitItem()).isEqualTo(PaymentSelection.Saved(newDefaultPaymentMethod))
}
}
}

private fun removeDuplicatesTest(shouldRemoveDuplicates: Boolean) {
val repository = FakeCustomerRepository()

Expand Down Expand Up @@ -591,7 +616,7 @@ class SavedPaymentMethodMutatorTest {
uiContext = coroutineContext,
customerRepository = customerRepository,
selection = selection,
clearSelection = { selection.value = null },
setSelection = { selection.value = it },
customerStateHolder = customerStateHolder,
prePaymentMethodRemoveActions = { prePaymentMethodRemovedTurbine.add(Unit) },
postPaymentMethodRemoveActions = { postPaymentMethodRemovedTurbine.add(Unit) },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ internal class VerticalModePage(
}

fun assertHasSavedPaymentMethods() {
composeTestRule.waitUntil {
composeTestRule.onNodeWithTag(TEST_TAG_SAVED_TEXT).isDisplayed()
}
composeTestRule.onNodeWithTag(TEST_TAG_SAVED_TEXT).assertExists()
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"id": "cus_Rkavqb9GNRJ2Ko",
"object": "customer",
"created": 1739227546,
"default_source": null,
"description": null,
"email": null,
"livemode": false,
"shipping": null
}

0 comments on commit 7048c91

Please sign in to comment.