Skip to content

Commit

Permalink
Support payment_method_remove_last in CustomerSheet (#9777)
Browse files Browse the repository at this point in the history
  • Loading branch information
samer-stripe authored Dec 12, 2024
1 parent b820a5a commit 82489c2
Show file tree
Hide file tree
Showing 22 changed files with 374 additions and 49 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ data class ElementsSession(
@Parcelize
data class Enabled(
val isPaymentMethodRemoveEnabled: Boolean,
val canRemoveLastPaymentMethod: Boolean,
) : CustomerSheet
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -273,9 +273,11 @@ internal class ElementsSessionJsonParser(
val customerSheetFeatures = json.optJSONObject(FIELD_FEATURES) ?: return null

val paymentMethodRemoveFeature = customerSheetFeatures.optString(FIELD_PAYMENT_METHOD_REMOVE)
val paymentMethodRemoveLastFeature = customerSheetFeatures.optString(FIELD_PAYMENT_METHOD_REMOVE_LAST)

ElementsSession.Customer.Components.CustomerSheet.Enabled(
isPaymentMethodRemoveEnabled = paymentMethodRemoveFeature == VALUE_ENABLED,
canRemoveLastPaymentMethod = paymentMethodRemoveLastFeature == VALUE_ENABLED,
)
} else {
ElementsSession.Customer.Components.CustomerSheet.Disabled
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -331,8 +331,11 @@ internal object ElementsSessionFixtures {
}
},
"customer_sheet": {
"enabled": false,
"features": null
"enabled": true,
"features": {
"payment_method_remove": ${paymentMethodRemoveFeature ?: "enabled"},
"payment_method_remove_last": ${paymentMethodRemoveLastFeature ?: "enabled"},
}
},
"pricing_table": {
"enabled": false
Expand Down Expand Up @@ -460,7 +463,8 @@ internal object ElementsSessionFixtures {
"customer_sheet": {
"enabled": true,
"features": {
"payment_method_remove": "enabled"
"payment_method_remove": "enabled",
"payment_method_remove_last": "enabled"
}
},
"pricing_table": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -544,7 +544,10 @@ class ElementsSessionJsonParserTest {
canRemoveLastPaymentMethod = true,
allowRedisplayOverride = PaymentMethod.AllowRedisplay.LIMITED,
),
customerSheet = ElementsSession.Customer.Components.CustomerSheet.Disabled
customerSheet = ElementsSession.Customer.Components.CustomerSheet.Enabled(
isPaymentMethodRemoveEnabled = true,
canRemoveLastPaymentMethod = true,
),
)
),
defaultPaymentMethod = "pm_123",
Expand Down Expand Up @@ -613,48 +616,48 @@ class ElementsSessionJsonParserTest {
}

@Test
fun `when 'payment_method_remove' is 'enabled' for MPE, 'canRemovePaymentMethods' should be true`() {
mobilePaymentElementPermissionsTest(
fun `when 'payment_method_remove' is 'enabled', 'canRemovePaymentMethods' should be true`() {
permissionsTest(
paymentMethodRemoveFeatureValue = "enabled",
canRemovePaymentMethods = true,
)
}

@Test
fun `when 'payment_method_remove' is 'disabled' for MPE, 'canRemovePaymentMethods' should be false`() {
mobilePaymentElementPermissionsTest(
fun `when 'payment_method_remove' is 'disabled', 'canRemovePaymentMethods' should be false`() {
permissionsTest(
paymentMethodRemoveFeatureValue = "disabled",
canRemovePaymentMethods = false,
)
}

@Test
fun `when 'payment_method_remove' is unknown value for MPE, 'canRemovePaymentMethods' should be false`() {
mobilePaymentElementPermissionsTest(
fun `when 'payment_method_remove' is unknown value, 'canRemovePaymentMethods' should be false`() {
permissionsTest(
paymentMethodRemoveFeatureValue = "something",
canRemovePaymentMethods = false,
)
}

@Test
fun `when 'payment_method_remove_last' is 'enabled' for MPE, 'canRemoveLastPaymentMethod' should be true`() {
mobilePaymentElementPermissionsTest(
fun `when 'payment_method_remove_last' is 'enabled', 'canRemoveLastPaymentMethod' should be true`() {
permissionsTest(
paymentMethodRemoveLastFeatureValue = "enabled",
canRemoveLastPaymentMethod = true,
)
}

@Test
fun `when 'payment_method_remove_last' is 'disabled' for MPE, 'canRemoveLastPaymentMethod' should be false`() {
mobilePaymentElementPermissionsTest(
fun `when 'payment_method_remove_last' is 'disabled', 'canRemoveLastPaymentMethod' should be false`() {
permissionsTest(
paymentMethodRemoveLastFeatureValue = "disabled",
canRemoveLastPaymentMethod = false,
)
}

@Test
fun `when 'payment_method_remove_last' is unknown value for MPE, 'canRemoveLastPaymentMethod' should be false`() {
mobilePaymentElementPermissionsTest(
fun `when 'payment_method_remove_last' is unknown value, 'canRemoveLastPaymentMethod' should be false`() {
permissionsTest(
paymentMethodRemoveLastFeatureValue = "something",
canRemoveLastPaymentMethod = false,
)
Expand Down Expand Up @@ -686,6 +689,7 @@ class ElementsSessionJsonParserTest {
mobilePaymentElement = ElementsSession.Customer.Components.MobilePaymentElement.Disabled,
customerSheet = ElementsSession.Customer.Components.CustomerSheet.Enabled(
isPaymentMethodRemoveEnabled = true,
canRemoveLastPaymentMethod = true,
),
)
),
Expand Down Expand Up @@ -826,7 +830,7 @@ class ElementsSessionJsonParserTest {
assertThat(enabledComponent?.allowRedisplayOverride).isEqualTo(allowRedisplay)
}

private fun mobilePaymentElementPermissionsTest(
private fun permissionsTest(
paymentMethodRemoveFeatureValue: String? = "enabled",
paymentMethodRemoveLastFeatureValue: String? = "enabled",
canRemovePaymentMethods: Boolean = true,
Expand All @@ -853,10 +857,21 @@ class ElementsSessionJsonParserTest {
assertThat(mobilePaymentElementComponent)
.isInstanceOf(ElementsSession.Customer.Components.MobilePaymentElement.Enabled::class.java)

val enabledComponent = mobilePaymentElementComponent as?
val enabledPaymentElementComponent = mobilePaymentElementComponent as?
ElementsSession.Customer.Components.MobilePaymentElement.Enabled

assertThat(enabledComponent?.isPaymentMethodRemoveEnabled).isEqualTo(canRemovePaymentMethods)
assertThat(enabledComponent?.canRemoveLastPaymentMethod).isEqualTo(canRemoveLastPaymentMethod)
assertThat(enabledPaymentElementComponent?.isPaymentMethodRemoveEnabled).isEqualTo(canRemovePaymentMethods)
assertThat(enabledPaymentElementComponent?.canRemoveLastPaymentMethod).isEqualTo(canRemoveLastPaymentMethod)

val customerSheetComponent = elementsSession?.customer?.session?.components?.customerSheet

assertThat(customerSheetComponent)
.isInstanceOf(ElementsSession.Customer.Components.CustomerSheet.Enabled::class.java)

val enabledCustomerSheetComponent = customerSheetComponent as?
ElementsSession.Customer.Components.CustomerSheet.Enabled

assertThat(enabledCustomerSheetComponent?.isPaymentMethodRemoveEnabled).isEqualTo(canRemovePaymentMethods)
assertThat(enabledCustomerSheetComponent?.canRemoveLastPaymentMethod).isEqualTo(canRemoveLastPaymentMethod)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ class CustomerEphemeralKeyRequest private constructor(
val paymentMethodSaveFeature: FeatureState?,
@SerialName("customer_session_payment_method_remove")
val paymentMethodRemoveFeature: FeatureState?,
@SerialName("customer_session_payment_method_remove_last")
val paymentMethodRemoveLastFeature: FeatureState?,
@SerialName("customer_session_payment_method_redisplay")
val paymentMethodRedisplayFeature: FeatureState?,
@SerialName("customer_session_payment_method_allow_redisplay_filters")
Expand All @@ -35,6 +37,7 @@ class CustomerEphemeralKeyRequest private constructor(
private var customerKeyType: CustomerKeyType = CustomerKeyType.Legacy
private var merchantCountryCode: String? = null
private var paymentMethodRemoveFeature: FeatureState = FeatureState.Enabled
private var paymentMethodRemoveLastFeature: FeatureState = FeatureState.Enabled
private var paymentMethodRedisplayFilters: List<AllowRedisplayFilter> = listOf(
AllowRedisplayFilter.Unspecified,
AllowRedisplayFilter.Limited,
Expand All @@ -53,6 +56,10 @@ class CustomerEphemeralKeyRequest private constructor(
this.paymentMethodRemoveFeature = state
}

fun paymentMethodRemoveLastFeature(state: FeatureState) {
this.paymentMethodRemoveLastFeature = state
}

fun paymentMethodRedisplayFilters(filters: List<AllowRedisplayFilter>) {
this.paymentMethodRedisplayFilters = filters
}
Expand All @@ -68,6 +75,7 @@ class CustomerEphemeralKeyRequest private constructor(
merchantCountryCode = merchantCountryCode,
paymentMethodSaveFeature = FeatureState.Enabled,
paymentMethodRemoveFeature = paymentMethodRemoveFeature,
paymentMethodRemoveLastFeature = paymentMethodRemoveLastFeature,
paymentMethodRedisplayFeature = FeatureState.Enabled,
paymentMethodRedisplayFilters = paymentMethodRedisplayFilters,
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.stripe.android.paymentsheet.example.playground.settings

import com.stripe.android.paymentsheet.example.playground.model.CheckoutRequest
import com.stripe.android.paymentsheet.example.playground.model.CustomerEphemeralKeyRequest
import com.stripe.android.paymentsheet.example.playground.model.FeatureState

internal object CustomerSessionRemoveLastSettingsDefinition : BooleanSettingsDefinition(
Expand All @@ -22,4 +23,12 @@ internal object CustomerSessionRemoveLastSettingsDefinition : BooleanSettingsDef
checkoutRequestBuilder.paymentMethodRemoveLastFeature(FeatureState.Disabled)
}
}

override fun configure(value: Boolean, customerEphemeralKeyRequestBuilder: CustomerEphemeralKeyRequest.Builder) {
if (value) {
customerEphemeralKeyRequestBuilder.paymentMethodRemoveLastFeature(FeatureState.Enabled)
} else {
customerEphemeralKeyRequestBuilder.paymentMethodRemoveLastFeature(FeatureState.Disabled)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ package com.stripe.android.customersheet

internal data class CustomerPermissions(
val canRemovePaymentMethods: Boolean,
val canRemoveLastPaymentMethod: Boolean,
)
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,10 @@ internal class DefaultCustomerSheetLoader(
configuration: CustomerSheet.Configuration
): Result<CustomerSheetState.Full> = workContext.runCatching {
val initializationDataSource = retrieveInitializationDataSource().getOrThrow()
var customerSheetSession = initializationDataSource.loadCustomerSheetSession().toResult().getOrThrow()
var customerSheetSession = initializationDataSource
.loadCustomerSheetSession(configuration)
.toResult()
.getOrThrow()

val filteredPaymentMethods = customerSheetSession.paymentMethods.filter {
PaymentSheetCardBrandFilter(configuration.cardBrandAcceptance).isAccepted(it)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ internal class CustomerSheetViewModel(
currentSelection = originalPaymentSelection,
permissions = CustomerPermissions(
canRemovePaymentMethods = false,
canRemoveLastPaymentMethod = false,
),
metadata = null,
)
Expand Down Expand Up @@ -1274,7 +1275,7 @@ internal class CustomerSheetViewModel(
) {
val canRemove = when (paymentMethods.size) {
0 -> false
1 -> configuration.allowsRemovalOfLastSavedPaymentMethod && permissions.canRemovePaymentMethods
1 -> permissions.canRemoveLastPaymentMethod && permissions.canRemovePaymentMethods
else -> permissions.canRemovePaymentMethods
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import com.stripe.android.core.injection.IOContext
import com.stripe.android.customersheet.CustomerAdapter
import com.stripe.android.customersheet.CustomerAdapter.PaymentOption.Companion.toPaymentOption
import com.stripe.android.customersheet.CustomerPermissions
import com.stripe.android.customersheet.CustomerSheet
import com.stripe.android.customersheet.map
import com.stripe.android.lpmfoundations.paymentmethod.PaymentMethodSaveConsentBehavior
import com.stripe.android.model.ElementsSession
Expand Down Expand Up @@ -34,7 +35,9 @@ internal class CustomerAdapterDataSource @Inject constructor(
CustomerSheetIntentDataSource {
override val canCreateSetupIntents: Boolean = customerAdapter.canCreateSetupIntents

override suspend fun loadCustomerSheetSession(): CustomerSheetDataResult<CustomerSheetSession> {
override suspend fun loadCustomerSheetSession(
configuration: CustomerSheet.Configuration,
): CustomerSheetDataResult<CustomerSheetSession> {
return workContext.runCatching {
val elementsSessionResult = async {
fetchElementsSession()
Expand All @@ -59,6 +62,7 @@ internal class CustomerAdapterDataSource @Inject constructor(
paymentMethodSaveConsentBehavior = PaymentMethodSaveConsentBehavior.Legacy,
savedSelection = savedSelection,
permissions = CustomerPermissions(
canRemoveLastPaymentMethod = configuration.allowsRemovalOfLastSavedPaymentMethod,
// Always `true` for `Adapter` use case
canRemovePaymentMethods = true,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.stripe.android.customersheet.data

import com.stripe.android.core.injection.IOContext
import com.stripe.android.customersheet.CustomerPermissions
import com.stripe.android.customersheet.CustomerSheet
import com.stripe.android.lpmfoundations.paymentmethod.PaymentMethodSaveConsentBehavior
import com.stripe.android.model.ElementsSession
import com.stripe.android.model.PaymentMethod
Expand All @@ -14,7 +15,9 @@ internal class CustomerSessionInitializationDataSource @Inject constructor(
private val savedSelectionDataSource: CustomerSheetSavedSelectionDataSource,
@IOContext private val workContext: CoroutineContext,
) : CustomerSheetInitializationDataSource {
override suspend fun loadCustomerSheetSession(): CustomerSheetDataResult<CustomerSheetSession> {
override suspend fun loadCustomerSheetSession(
configuration: CustomerSheet.Configuration,
): CustomerSheetDataResult<CustomerSheetSession> {
return withContext(workContext) {
elementsSessionManager.fetchElementsSession().mapCatching { customerSessionElementsSession ->
val savedSelection = savedSelectionDataSource
Expand All @@ -24,6 +27,14 @@ internal class CustomerSessionInitializationDataSource @Inject constructor(

val customer = customerSessionElementsSession.customer

val canRemoveLastPaymentMethodFromCustomerSession = when (
val component = customer.session.components.customerSheet
) {
is ElementsSession.Customer.Components.CustomerSheet.Enabled ->
component.canRemoveLastPaymentMethod
is ElementsSession.Customer.Components.CustomerSheet.Disabled -> false
}

CustomerSheetSession(
elementsSession = customerSessionElementsSession.elementsSession,
paymentMethods = customer.paymentMethods,
Expand All @@ -37,6 +48,8 @@ internal class CustomerSessionInitializationDataSource @Inject constructor(
),
savedSelection = savedSelection,
permissions = CustomerPermissions(
canRemoveLastPaymentMethod = configuration.allowsRemovalOfLastSavedPaymentMethod &&
canRemoveLastPaymentMethodFromCustomerSession,
canRemovePaymentMethods = when (val component = customer.session.components.customerSheet) {
is ElementsSession.Customer.Components.CustomerSheet.Enabled ->
component.isPaymentMethodRemoveEnabled
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package com.stripe.android.customersheet.data

import com.stripe.android.customersheet.CustomerSheet

internal interface CustomerSheetInitializationDataSource {
suspend fun loadCustomerSheetSession(): CustomerSheetDataResult<CustomerSheetSession>
suspend fun loadCustomerSheetSession(
configuration: CustomerSheet.Configuration,
): CustomerSheetDataResult<CustomerSheetSession>
}
Loading

0 comments on commit 82489c2

Please sign in to comment.