Skip to content

Commit

Permalink
[MOBILESDK-2894] [Android] Make Default Payment Method first (#9928)
Browse files Browse the repository at this point in the history
* ordering of default payment methods

* testOrderingOfPaymentMethodsWithDefaultPaymentMethodId

* better ordering logic

edge case where defaultPaymentMethodIndex not found and there is a saved selection Index, and we ignore the savedSelectionIndex

* rename withLastUsedPaymentMethodFirst to withDefaultPaymentMethodOrLastUsedPaymentMethodFirst

* use a hardcoded input and expected output

* lint
  • Loading branch information
tianzhao-stripe authored Jan 17, 2025
1 parent 1d4d941 commit 3a0e9e3
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,10 @@ internal class DefaultPaymentElementLoader @Inject constructor(
return customerState?.let { state ->
state.copy(
paymentMethods = state.paymentMethods
.withLastUsedPaymentMethodFirst(savedSelection.await()).filter { cardBrandFilter.isAccepted(it) }
.withDefaultPaymentMethodOrLastUsedPaymentMethodFirst(
savedSelection = savedSelection.await(),
defaultPaymentMethodId = state.defaultPaymentMethodId
).filter { cardBrandFilter.isAccepted(it) }
)
}
}
Expand Down Expand Up @@ -715,19 +718,24 @@ internal class DefaultPaymentElementLoader @Inject constructor(
}
}

private fun List<PaymentMethod>.withLastUsedPaymentMethodFirst(
private fun List<PaymentMethod>.withDefaultPaymentMethodOrLastUsedPaymentMethodFirst(
savedSelection: SavedSelection,
defaultPaymentMethodId: String?
): List<PaymentMethod> {
val defaultPaymentMethodIndex = (savedSelection as? SavedSelection.PaymentMethod)?.let {
val defaultPaymentMethodIndex = defaultPaymentMethodId?.let {
indexOfFirst { it.id == defaultPaymentMethodId }
}

val savedSelectionIndex = (savedSelection as? SavedSelection.PaymentMethod)?.let {
indexOfFirst { it.id == savedSelection.id }.takeIf { it != -1 }
}

return if (defaultPaymentMethodIndex != null) {
val primaryPaymentMethod = get(defaultPaymentMethodIndex)
val primaryPaymentMethodIndex = defaultPaymentMethodIndex ?: savedSelectionIndex

return primaryPaymentMethodIndex?.let {
val primaryPaymentMethod = get(primaryPaymentMethodIndex)
listOf(primaryPaymentMethod) + (this - primaryPaymentMethod)
} else {
this
}
} ?: this
}

private fun PaymentMethod.toPaymentSelection(): PaymentSelection.Saved {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import com.stripe.android.common.model.asCommonConfiguration
import com.stripe.android.core.Logger
import com.stripe.android.core.exception.APIConnectionException
import com.stripe.android.core.model.CountryCode
import com.stripe.android.core.utils.FeatureFlags
import com.stripe.android.googlepaylauncher.GooglePayRepository
import com.stripe.android.isInstanceOf
import com.stripe.android.link.LinkConfiguration
Expand Down Expand Up @@ -41,6 +42,7 @@ import com.stripe.android.paymentsheet.repositories.ElementsSessionRepository
import com.stripe.android.paymentsheet.state.PaymentSheetLoadingException.PaymentIntentInTerminalState
import com.stripe.android.paymentsheet.utils.FakeUserFacingLogger
import com.stripe.android.testing.FakeErrorReporter
import com.stripe.android.testing.FeatureFlagTestRule
import com.stripe.android.testing.PaymentMethodFactory
import com.stripe.android.testing.PaymentMethodFactory.update
import com.stripe.android.ui.core.cbc.CardBrandChoiceEligibility
Expand All @@ -50,6 +52,7 @@ import com.stripe.android.utils.FakeElementsSessionRepository
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Rule
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.eq
import org.mockito.Captor
Expand Down Expand Up @@ -80,6 +83,12 @@ internal class DefaultPaymentElementLoaderTest {
private val readyGooglePayRepository = mock<GooglePayRepository>()
private val unreadyGooglePayRepository = mock<GooglePayRepository>()

@get:Rule
val enableDefaultPaymentMethods = FeatureFlagTestRule(
featureFlag = FeatureFlags.enableDefaultPaymentMethods,
isEnabled = false,
)

@BeforeTest
fun setup() {
MockitoAnnotations.openMocks(this)
Expand Down Expand Up @@ -1944,6 +1953,22 @@ internal class DefaultPaymentElementLoaderTest {
assertThat(repository.lastParams?.defaultPaymentMethodId).isNull()
}

@Test
fun `When DefaultPaymentMethod not null, no saved selection, paymentMethod order correct`() = runTest {
enableDefaultPaymentMethods.setEnabled(true)

testOrderingOfPaymentMethodsWithDefaultPaymentMethodId()
}

@Test
fun `When DefaultPaymentMethod not null, and saved selection, paymentMethod order correct`() = runTest {
enableDefaultPaymentMethods.setEnabled(true)

testOrderingOfPaymentMethodsWithDefaultPaymentMethodId(
setLastUsedIndex = 1
)
}

@Test
fun `When using 'LegacyEphemeralKey' & has a default saved Stripe payment method, should not call 'ElementsSessionRepository' with default id`() =
runTest {
Expand Down Expand Up @@ -2466,4 +2491,55 @@ internal class DefaultPaymentElementLoaderTest {
isReloadingAfterProcessDeath = isReloadingAfterProcessDeath,
initializedViaCompose = initializedViaCompose,
)

private val paymentMethodsForTestingOrdering = listOf(
PaymentMethodFixtures.CARD_PAYMENT_METHOD.copy(id = "a1", customerId = "alice"),
PaymentMethodFixtures.CARD_PAYMENT_METHOD.copy(id = "b2", customerId = "bob"),
PaymentMethodFixtures.CARD_PAYMENT_METHOD.copy(id = "c3", customerId = "carol"),
PaymentMethodFixtures.CARD_PAYMENT_METHOD.copy(id = "d4", customerId = "dan")
)

@OptIn(ExperimentalCustomerSessionApi::class)
private suspend fun testOrderingOfPaymentMethodsWithDefaultPaymentMethodId(
setLastUsedIndex: Int? = null
) {
val defaultPaymentMethodIndex = 2
val defaultPaymentMethod = paymentMethodsForTestingOrdering[defaultPaymentMethodIndex]
val defaultPaymentMethodId = defaultPaymentMethod.id

if (setLastUsedIndex != null && setLastUsedIndex in paymentMethodsForTestingOrdering.indices) {
val lastUsed = paymentMethodsForTestingOrdering[setLastUsedIndex]
prefsRepository.savePaymentSelection(PaymentSelection.Saved(lastUsed))
}

val loader = createPaymentElementLoader(
customer = ElementsSession.Customer(
paymentMethods = paymentMethodsForTestingOrdering,
session = createElementsSessionCustomerSession(
mobilePaymentElementComponent = ElementsSession.Customer.Components.MobilePaymentElement.Disabled,
),
defaultPaymentMethod = defaultPaymentMethodId,
)
)

val result = loader.load(
initializationMode = PaymentElementLoader.InitializationMode.PaymentIntent("secret"),
paymentSheetConfiguration = mockConfiguration(
customer = PaymentSheet.CustomerConfiguration.createWithCustomerSession(
id = "id",
clientSecret = "cuss_1",
),
),
initializedViaCompose = false,
).getOrThrow()

val observedElements = result.customer?.paymentMethods
val expectedElements = listOf(
PaymentMethodFixtures.CARD_PAYMENT_METHOD.copy(id = "c3", customerId = "carol"),
PaymentMethodFixtures.CARD_PAYMENT_METHOD.copy(id = "a1", customerId = "alice"),
PaymentMethodFixtures.CARD_PAYMENT_METHOD.copy(id = "b2", customerId = "bob"),
PaymentMethodFixtures.CARD_PAYMENT_METHOD.copy(id = "d4", customerId = "dan")
)
assertThat(observedElements).containsExactlyElementsIn(expectedElements).inOrder()
}
}

0 comments on commit 3a0e9e3

Please sign in to comment.