diff --git a/payments-core/detekt-baseline.xml b/payments-core/detekt-baseline.xml
index 4f9819afc73..d89c370befd 100644
--- a/payments-core/detekt-baseline.xml
+++ b/payments-core/detekt-baseline.xml
@@ -34,7 +34,7 @@
LongMethod:CardFormViewTest.kt$CardFormViewTest$@Test fun testCardValidCallback()
LongMethod:CardInputWidget.kt$CardInputWidget$private fun initView(attrs: AttributeSet?)
LongMethod:CollectBankAccountViewModel.kt$CollectBankAccountViewModel$private suspend fun createFinancialConnectionsSession()
- LongMethod:ElementsSessionFixtures.kt$ElementsSessionFixtures$fun createPaymentIntentWithCustomerSession( allowRedisplay: String? = "limited" ): JSONObject
+ LongMethod:ElementsSessionFixtures.kt$ElementsSessionFixtures$fun createPaymentIntentWithCustomerSession( allowRedisplay: String? = "limited", paymentMethodRemoveFeature: String? = "enabled", paymentMethodRemoveLastFeature: String? = "enabled", ): JSONObject
LongMethod:ElementsSessionJsonParserTest.kt$ElementsSessionJsonParserTest$@Test fun `ElementsSession has expected customer session information in the response`()
LongMethod:GooglePayJsonFactoryTest.kt$GooglePayJsonFactoryTest$@Test fun testCreatePaymentMethodRequestJson()
LongMethod:PaymentAuthConfigTest.kt$PaymentAuthConfigTest$@Test fun testUiCustomizationWrapper()
diff --git a/payments-core/src/main/java/com/stripe/android/model/ElementsSession.kt b/payments-core/src/main/java/com/stripe/android/model/ElementsSession.kt
index 943770fa452..ec929159017 100644
--- a/payments-core/src/main/java/com/stripe/android/model/ElementsSession.kt
+++ b/payments-core/src/main/java/com/stripe/android/model/ElementsSession.kt
@@ -90,6 +90,7 @@ data class ElementsSession(
data class Enabled(
val isPaymentMethodSaveEnabled: Boolean,
val isPaymentMethodRemoveEnabled: Boolean,
+ val canRemoveLastPaymentMethod: Boolean,
val allowRedisplayOverride: PaymentMethod.AllowRedisplay?
) : MobilePaymentElement
}
diff --git a/payments-core/src/main/java/com/stripe/android/model/parsers/ElementsSessionJsonParser.kt b/payments-core/src/main/java/com/stripe/android/model/parsers/ElementsSessionJsonParser.kt
index 86da90f689e..def7634258b 100644
--- a/payments-core/src/main/java/com/stripe/android/model/parsers/ElementsSessionJsonParser.kt
+++ b/payments-core/src/main/java/com/stripe/android/model/parsers/ElementsSessionJsonParser.kt
@@ -243,6 +243,7 @@ internal class ElementsSessionJsonParser(
val paymentMethodSaveFeature = paymentSheetFeatures.optString(FIELD_PAYMENT_METHOD_SAVE)
val paymentMethodRemoveFeature = paymentSheetFeatures.optString(FIELD_PAYMENT_METHOD_REMOVE)
+ val paymentMethodRemoveLastFeature = paymentSheetFeatures.optString(FIELD_PAYMENT_METHOD_REMOVE_LAST)
val allowRedisplayOverrideValue = paymentSheetFeatures
.optString(FIELD_PAYMENT_METHOD_ALLOW_REDISPLAY_OVERRIDE)
@@ -253,6 +254,7 @@ internal class ElementsSessionJsonParser(
ElementsSession.Customer.Components.MobilePaymentElement.Enabled(
isPaymentMethodSaveEnabled = paymentMethodSaveFeature == VALUE_ENABLED,
isPaymentMethodRemoveEnabled = paymentMethodRemoveFeature == VALUE_ENABLED,
+ canRemoveLastPaymentMethod = paymentMethodRemoveLastFeature == VALUE_ENABLED,
allowRedisplayOverride = allowRedisplayOverride,
)
} else {
@@ -349,6 +351,7 @@ internal class ElementsSessionJsonParser(
private const val FIELD_PAYMENT_METHOD_REMOVE = "payment_method_remove"
private const val FIELD_PAYMENT_METHOD_ALLOW_REDISPLAY_OVERRIDE =
"payment_method_save_allow_redisplay_override"
+ private const val FIELD_PAYMENT_METHOD_REMOVE_LAST = "payment_method_remove_last"
private const val VALUE_ENABLED = FIELD_ENABLED
const val FIELD_GOOGLE_PAY_PREFERENCE = "google_pay_preference"
diff --git a/payments-core/src/test/java/com/stripe/android/model/ElementsSessionFixtures.kt b/payments-core/src/test/java/com/stripe/android/model/ElementsSessionFixtures.kt
index a9831c0a6f5..d92b81cf8d5 100644
--- a/payments-core/src/test/java/com/stripe/android/model/ElementsSessionFixtures.kt
+++ b/payments-core/src/test/java/com/stripe/android/model/ElementsSessionFixtures.kt
@@ -240,7 +240,9 @@ internal object ElementsSessionFixtures {
)
fun createPaymentIntentWithCustomerSession(
- allowRedisplay: String? = "limited"
+ allowRedisplay: String? = "limited",
+ paymentMethodRemoveFeature: String? = "enabled",
+ paymentMethodRemoveLastFeature: String? = "enabled",
): JSONObject {
return JSONObject(
"""
@@ -322,8 +324,9 @@ internal object ElementsSessionFixtures {
"mobile_payment_element": {
"enabled": true,
"features": {
- "payment_method_remove": "enabled",
+ "payment_method_remove": ${paymentMethodRemoveFeature ?: "enabled"},
"payment_method_save": "disabled",
+ "payment_method_remove_last": ${paymentMethodRemoveLastFeature ?: "enabled"},
"payment_method_save_allow_redisplay_override": ${allowRedisplay?.let { "\"$it\""} ?: "null"}
}
},
diff --git a/payments-core/src/test/java/com/stripe/android/model/parsers/ElementsSessionJsonParserTest.kt b/payments-core/src/test/java/com/stripe/android/model/parsers/ElementsSessionJsonParserTest.kt
index b1400e11f0a..ff82c92e12a 100644
--- a/payments-core/src/test/java/com/stripe/android/model/parsers/ElementsSessionJsonParserTest.kt
+++ b/payments-core/src/test/java/com/stripe/android/model/parsers/ElementsSessionJsonParserTest.kt
@@ -541,6 +541,7 @@ class ElementsSessionJsonParserTest {
mobilePaymentElement = ElementsSession.Customer.Components.MobilePaymentElement.Enabled(
isPaymentMethodSaveEnabled = false,
isPaymentMethodRemoveEnabled = true,
+ canRemoveLastPaymentMethod = true,
allowRedisplayOverride = PaymentMethod.AllowRedisplay.LIMITED,
),
customerSheet = ElementsSession.Customer.Components.CustomerSheet.Disabled
@@ -611,6 +612,54 @@ class ElementsSessionJsonParserTest {
)
}
+ @Test
+ fun `when 'payment_method_remove' is 'enabled' for MPE, 'canRemovePaymentMethods' should be true`() {
+ mobilePaymentElementPermissionsTest(
+ paymentMethodRemoveFeatureValue = "enabled",
+ canRemovePaymentMethods = true,
+ )
+ }
+
+ @Test
+ fun `when 'payment_method_remove' is 'disabled' for MPE, 'canRemovePaymentMethods' should be false`() {
+ mobilePaymentElementPermissionsTest(
+ paymentMethodRemoveFeatureValue = "disabled",
+ canRemovePaymentMethods = false,
+ )
+ }
+
+ @Test
+ fun `when 'payment_method_remove' is unknown value for MPE, 'canRemovePaymentMethods' should be false`() {
+ mobilePaymentElementPermissionsTest(
+ paymentMethodRemoveFeatureValue = "something",
+ canRemovePaymentMethods = false,
+ )
+ }
+
+ @Test
+ fun `when 'payment_method_remove_last' is 'enabled' for MPE, 'canRemoveLastPaymentMethod' should be true`() {
+ mobilePaymentElementPermissionsTest(
+ paymentMethodRemoveLastFeatureValue = "enabled",
+ canRemoveLastPaymentMethod = true,
+ )
+ }
+
+ @Test
+ fun `when 'payment_method_remove_last' is 'disabled' for MPE, 'canRemoveLastPaymentMethod' should be false`() {
+ mobilePaymentElementPermissionsTest(
+ paymentMethodRemoveLastFeatureValue = "disabled",
+ canRemoveLastPaymentMethod = false,
+ )
+ }
+
+ @Test
+ fun `when 'payment_method_remove_last' is unknown value for MPE, 'canRemoveLastPaymentMethod' should be false`() {
+ mobilePaymentElementPermissionsTest(
+ paymentMethodRemoveLastFeatureValue = "something",
+ canRemoveLastPaymentMethod = false,
+ )
+ }
+
@Test
fun `ElementsSession has expected customer session information with customer sheet component in the response`() {
val parser = ElementsSessionJsonParser(
@@ -636,7 +685,7 @@ class ElementsSessionJsonParserTest {
components = ElementsSession.Customer.Components(
mobilePaymentElement = ElementsSession.Customer.Components.MobilePaymentElement.Disabled,
customerSheet = ElementsSession.Customer.Components.CustomerSheet.Enabled(
- isPaymentMethodRemoveEnabled = true
+ isPaymentMethodRemoveEnabled = true,
),
)
),
@@ -776,4 +825,38 @@ class ElementsSessionJsonParserTest {
assertThat(enabledComponent?.allowRedisplayOverride).isEqualTo(allowRedisplay)
}
+
+ private fun mobilePaymentElementPermissionsTest(
+ paymentMethodRemoveFeatureValue: String? = "enabled",
+ paymentMethodRemoveLastFeatureValue: String? = "enabled",
+ canRemovePaymentMethods: Boolean = true,
+ canRemoveLastPaymentMethod: Boolean = true,
+ ) {
+ val parser = ElementsSessionJsonParser(
+ ElementsSessionParams.PaymentIntentType(
+ clientSecret = "secret",
+ customerSessionClientSecret = "customer_session_client_secret",
+ externalPaymentMethods = emptyList(),
+ ),
+ isLiveMode = false,
+ )
+
+ val intent = createPaymentIntentWithCustomerSession(
+ paymentMethodRemoveFeature = paymentMethodRemoveFeatureValue,
+ paymentMethodRemoveLastFeature = paymentMethodRemoveLastFeatureValue,
+ )
+
+ val elementsSession = parser.parse(intent)
+
+ val mobilePaymentElementComponent = elementsSession?.customer?.session?.components?.mobilePaymentElement
+
+ assertThat(mobilePaymentElementComponent)
+ .isInstanceOf(ElementsSession.Customer.Components.MobilePaymentElement.Enabled::class.java)
+
+ val enabledComponent = mobilePaymentElementComponent as?
+ ElementsSession.Customer.Components.MobilePaymentElement.Enabled
+
+ assertThat(enabledComponent?.isPaymentMethodRemoveEnabled).isEqualTo(canRemovePaymentMethods)
+ assertThat(enabledComponent?.canRemoveLastPaymentMethod).isEqualTo(canRemoveLastPaymentMethod)
+ }
}
diff --git a/paymentsheet-example/src/main/java/com/stripe/android/paymentsheet/example/playground/model/PlaygroundCheckoutModel.kt b/paymentsheet-example/src/main/java/com/stripe/android/paymentsheet/example/playground/model/PlaygroundCheckoutModel.kt
index 3c539c9f9ed..fc9f3d7d468 100644
--- a/paymentsheet-example/src/main/java/com/stripe/android/paymentsheet/example/playground/model/PlaygroundCheckoutModel.kt
+++ b/paymentsheet-example/src/main/java/com/stripe/android/paymentsheet/example/playground/model/PlaygroundCheckoutModel.kt
@@ -38,6 +38,8 @@ class CheckoutRequest 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")
@@ -69,6 +71,7 @@ class CheckoutRequest private constructor(
private var requireCvcRecollection: Boolean? = null
private var paymentMethodSaveFeature: FeatureState = FeatureState.Enabled
private var paymentMethodRemoveFeature: FeatureState = FeatureState.Enabled
+ private var paymentMethodRemoveLastFeature: FeatureState = FeatureState.Enabled
private var paymentMethodRedisplayFeature: FeatureState = FeatureState.Enabled
private var paymentMethodRedisplayFilters: List = listOf(
AllowRedisplayFilter.Unspecified,
@@ -129,6 +132,10 @@ class CheckoutRequest private constructor(
this.paymentMethodRemoveFeature = state
}
+ fun paymentMethodRemoveLastFeature(state: FeatureState) {
+ this.paymentMethodRemoveLastFeature = state
+ }
+
fun paymentMethodRedisplayFeature(state: FeatureState) {
this.paymentMethodRedisplayFeature = state
}
@@ -162,6 +169,7 @@ class CheckoutRequest private constructor(
customerSessionComponentName = "mobile_payment_element",
paymentMethodSaveFeature = paymentMethodSaveFeature,
paymentMethodRemoveFeature = paymentMethodRemoveFeature,
+ paymentMethodRemoveLastFeature = paymentMethodRemoveLastFeature,
paymentMethodRedisplayFeature = paymentMethodRedisplayFeature,
paymentMethodRedisplayFilters = paymentMethodRedisplayFilters,
paymentMethodOverrideRedisplay = paymentMethodOverrideRedisplay,
diff --git a/paymentsheet-example/src/main/java/com/stripe/android/paymentsheet/example/playground/settings/CustomerSessionRemoveLastSettingsDefinition.kt b/paymentsheet-example/src/main/java/com/stripe/android/paymentsheet/example/playground/settings/CustomerSessionRemoveLastSettingsDefinition.kt
new file mode 100644
index 00000000000..d88f1921c06
--- /dev/null
+++ b/paymentsheet-example/src/main/java/com/stripe/android/paymentsheet/example/playground/settings/CustomerSessionRemoveLastSettingsDefinition.kt
@@ -0,0 +1,25 @@
+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.FeatureState
+
+internal object CustomerSessionRemoveLastSettingsDefinition : BooleanSettingsDefinition(
+ defaultValue = true,
+ displayName = "Customer Session Remove Last Payment Method",
+ key = "customer_session_payment_method_remove"
+) {
+ override fun createOptions(
+ configurationData: PlaygroundConfigurationData
+ ) = listOf(
+ PlaygroundSettingDefinition.Displayable.Option("Enabled", true),
+ PlaygroundSettingDefinition.Displayable.Option("Disabled", false),
+ )
+
+ override fun configure(value: Boolean, checkoutRequestBuilder: CheckoutRequest.Builder) {
+ if (value) {
+ checkoutRequestBuilder.paymentMethodRemoveLastFeature(FeatureState.Enabled)
+ } else {
+ checkoutRequestBuilder.paymentMethodRemoveLastFeature(FeatureState.Disabled)
+ }
+ }
+}
diff --git a/paymentsheet-example/src/main/java/com/stripe/android/paymentsheet/example/playground/settings/PlaygroundSettings.kt b/paymentsheet-example/src/main/java/com/stripe/android/paymentsheet/example/playground/settings/PlaygroundSettings.kt
index df3b9c9749a..f398642e150 100644
--- a/paymentsheet-example/src/main/java/com/stripe/android/paymentsheet/example/playground/settings/PlaygroundSettings.kt
+++ b/paymentsheet-example/src/main/java/com/stripe/android/paymentsheet/example/playground/settings/PlaygroundSettings.kt
@@ -418,6 +418,7 @@ internal class PlaygroundSettings private constructor(
CustomerSessionSettingsDefinition,
CustomerSessionSaveSettingsDefinition,
CustomerSessionRemoveSettingsDefinition,
+ CustomerSessionRemoveLastSettingsDefinition,
CustomerSessionRedisplaySettingsDefinition,
CustomerSessionRedisplayFiltersSettingsDefinition,
CustomerSessionOverrideRedisplaySettingsDefinition,
diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/SavedPaymentMethodMutator.kt b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/SavedPaymentMethodMutator.kt
index a8576417109..1d93ecb5d45 100644
--- a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/SavedPaymentMethodMutator.kt
+++ b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/SavedPaymentMethodMutator.kt
@@ -34,7 +34,6 @@ internal class SavedPaymentMethodMutator(
private val coroutineScope: CoroutineScope,
private val workContext: CoroutineContext,
private val customerRepository: CustomerRepository,
- private val allowsRemovalOfLastSavedPaymentMethod: Boolean,
private val selection: StateFlow,
val providePaymentMethodName: (PaymentMethodCode?) -> ResolvableString,
private val clearSelection: () -> Unit,
@@ -62,10 +61,11 @@ internal class SavedPaymentMethodMutator(
val canRemove: StateFlow = customerStateHolder.customer.mapAsStateFlow { customerState ->
customerState?.run {
val hasRemovePermissions = customerState.permissions.canRemovePaymentMethods
+ val hasRemoveLastPaymentMethodPermissions = customerState.permissions.canRemoveLastPaymentMethod
when (paymentMethods.size) {
0 -> false
- 1 -> allowsRemovalOfLastSavedPaymentMethod && hasRemovePermissions
+ 1 -> hasRemoveLastPaymentMethodPermissions && hasRemovePermissions
else -> hasRemovePermissions
}
} ?: false
@@ -390,7 +390,6 @@ internal class SavedPaymentMethodMutator(
coroutineScope = viewModel.viewModelScope,
workContext = viewModel.workContext,
customerRepository = viewModel.customerRepository,
- allowsRemovalOfLastSavedPaymentMethod = viewModel.config.allowsRemovalOfLastSavedPaymentMethod,
selection = viewModel.selection,
providePaymentMethodName = { code ->
code?.let {
diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/state/CustomerState.kt b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/state/CustomerState.kt
index 1ea59674b87..4d5b29ef296 100644
--- a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/state/CustomerState.kt
+++ b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/state/CustomerState.kt
@@ -1,6 +1,7 @@
package com.stripe.android.paymentsheet.state
import android.os.Parcelable
+import com.stripe.android.common.model.CommonConfiguration
import com.stripe.android.model.ElementsSession
import com.stripe.android.model.PaymentMethod
import com.stripe.android.paymentsheet.PaymentSheet
@@ -16,6 +17,7 @@ internal data class CustomerState(
@Parcelize
data class Permissions(
val canRemovePaymentMethods: Boolean,
+ val canRemoveLastPaymentMethod: Boolean,
val canRemoveDuplicates: Boolean,
) : Parcelable
@@ -28,17 +30,26 @@ internal data class CustomerState(
* @return [CustomerState] instance using [ElementsSession.Customer] data
*/
internal fun createForCustomerSession(
+ configuration: CommonConfiguration,
customer: ElementsSession.Customer,
supportedSavedPaymentMethodTypes: List,
): CustomerState {
- val canRemovePaymentMethods = when (
- val mobilePaymentElementComponent = customer.session.components.mobilePaymentElement
- ) {
- is ElementsSession.Customer.Components.MobilePaymentElement.Enabled ->
+ val mobilePaymentElementComponent = customer.session.components.mobilePaymentElement
+
+ val canRemovePaymentMethods = when (mobilePaymentElementComponent) {
+ is ElementsSession.Customer.Components.MobilePaymentElement.Enabled -> {
mobilePaymentElementComponent.isPaymentMethodRemoveEnabled
+ }
is ElementsSession.Customer.Components.MobilePaymentElement.Disabled -> false
}
+ val canRemoveLastPaymentMethod = when {
+ !configuration.allowsRemovalOfLastSavedPaymentMethod -> false
+ mobilePaymentElementComponent is ElementsSession.Customer.Components.MobilePaymentElement.Enabled ->
+ mobilePaymentElementComponent.canRemoveLastPaymentMethod
+ else -> false
+ }
+
return CustomerState(
id = customer.session.customerId,
ephemeralKeySecret = customer.session.apiKey,
@@ -47,6 +58,7 @@ internal data class CustomerState(
},
permissions = Permissions(
canRemovePaymentMethods = canRemovePaymentMethods,
+ canRemoveLastPaymentMethod = canRemoveLastPaymentMethod,
// Should always remove duplicates when using `customer_session`
canRemoveDuplicates = true,
)
@@ -63,6 +75,7 @@ internal data class CustomerState(
* @return [CustomerState] instance with legacy ephemeral key secrets
*/
internal fun createForLegacyEphemeralKey(
+ configuration: CommonConfiguration,
customerId: String,
accessType: PaymentSheet.CustomerAccessType.LegacyCustomerEphemeralKey,
paymentMethods: List,
@@ -77,6 +90,13 @@ internal data class CustomerState(
* always be set to true.
*/
canRemovePaymentMethods = true,
+ /*
+ * Un-scoped legacy ephemeral keys normally have full permissions to remove the last payment
+ * method, however we do have client-side configuration option to configure this ability. This
+ * should eventually be removed in favor of the server-side option available with customer
+ * sessions.
+ */
+ canRemoveLastPaymentMethod = configuration.allowsRemovalOfLastSavedPaymentMethod,
/*
* Removing duplicates is not applicable here since we don't filter out duplicates for for
* un-scoped ephemeral keys.
diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/state/PaymentElementLoader.kt b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/state/PaymentElementLoader.kt
index 15597ff3fbd..4ff4cda8b52 100644
--- a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/state/PaymentElementLoader.kt
+++ b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/state/PaymentElementLoader.kt
@@ -189,6 +189,7 @@ internal class DefaultPaymentElementLoader @Inject constructor(
customerInfo = customerInfo,
metadata = metadata.await(),
savedSelection = savedSelection,
+ configuration = configuration,
cardBrandFilter = PaymentSheetCardBrandFilter(configuration.cardBrandAcceptance)
)
}
@@ -313,6 +314,7 @@ internal class DefaultPaymentElementLoader @Inject constructor(
}
private suspend fun createCustomerState(
+ configuration: CommonConfiguration,
customerInfo: CustomerInfo?,
metadata: PaymentMethodMetadata,
savedSelection: Deferred,
@@ -322,6 +324,7 @@ internal class DefaultPaymentElementLoader @Inject constructor(
is CustomerInfo.CustomerSession -> {
CustomerState.createForCustomerSession(
customer = customerInfo.elementsSessionCustomer,
+ configuration = configuration,
supportedSavedPaymentMethodTypes = metadata.supportedSavedPaymentMethodTypes()
)
}
@@ -329,6 +332,7 @@ internal class DefaultPaymentElementLoader @Inject constructor(
CustomerState.createForLegacyEphemeralKey(
customerId = customerInfo.id,
accessType = customerInfo.accessType,
+ configuration = configuration,
paymentMethods = retrieveCustomerPaymentMethods(
metadata = metadata,
customerConfig = customerInfo.customerConfig,
diff --git a/paymentsheet/src/test/java/com/stripe/android/lpmfoundations/paymentmethod/PaymentMethodMetadataTest.kt b/paymentsheet/src/test/java/com/stripe/android/lpmfoundations/paymentmethod/PaymentMethodMetadataTest.kt
index 32b4490135f..ad813b2c75a 100644
--- a/paymentsheet/src/test/java/com/stripe/android/lpmfoundations/paymentmethod/PaymentMethodMetadataTest.kt
+++ b/paymentsheet/src/test/java/com/stripe/android/lpmfoundations/paymentmethod/PaymentMethodMetadataTest.kt
@@ -867,6 +867,7 @@ internal class PaymentMethodMetadataTest {
mobilePaymentElementComponent = ElementsSession.Customer.Components.MobilePaymentElement.Enabled(
isPaymentMethodSaveEnabled = true,
isPaymentMethodRemoveEnabled = true,
+ canRemoveLastPaymentMethod = true,
allowRedisplayOverride = null,
)
)
@@ -880,6 +881,7 @@ internal class PaymentMethodMetadataTest {
mobilePaymentElementComponent = ElementsSession.Customer.Components.MobilePaymentElement.Enabled(
isPaymentMethodSaveEnabled = false,
isPaymentMethodRemoveEnabled = true,
+ canRemoveLastPaymentMethod = true,
allowRedisplayOverride = null,
),
)
diff --git a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/CustomerSessionPaymentSheetActivityTest.kt b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/CustomerSessionPaymentSheetActivityTest.kt
index f3ee2fdb662..46524fc5940 100644
--- a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/CustomerSessionPaymentSheetActivityTest.kt
+++ b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/CustomerSessionPaymentSheetActivityTest.kt
@@ -58,7 +58,8 @@ internal class CustomerSessionPaymentSheetActivityTest {
PaymentMethodFactory.card(last4 = "5544", addCbcNetworks = true),
),
isPaymentMethodRemoveEnabled = true,
- allowsRemovalOfLastSavedPaymentMethod = true,
+ canRemoveLastPaymentMethodConfig = true,
+ canRemoveLastPaymentMethodServer = true,
) {
composeTestRule.onEditButton().performClick()
@@ -67,19 +68,46 @@ internal class CustomerSessionPaymentSheetActivityTest {
}
@Test
- fun `When single PM with remove permissions and can remove last PM, should be enabled when editing`() =
+ fun `When single PM with remove permissions and can remove last from sources, should be enabled when editing`() =
runTest(
cards = listOf(
PaymentMethodFactory.card(last4 = "4242"),
),
isPaymentMethodRemoveEnabled = true,
- allowsRemovalOfLastSavedPaymentMethod = true,
+ canRemoveLastPaymentMethodConfig = true,
+ canRemoveLastPaymentMethodServer = true,
) {
composeTestRule.onEditButton().performClick()
composeTestRule.onSavedPaymentMethod(last4 = "4242").assertIsEnabled()
}
+ @Test
+ fun `When single PM with remove permissions and cannot remove last PM from server, edit should not be shown`() =
+ runTest(
+ cards = listOf(
+ PaymentMethodFactory.card(last4 = "4242"),
+ ),
+ isPaymentMethodRemoveEnabled = true,
+ canRemoveLastPaymentMethodConfig = true,
+ canRemoveLastPaymentMethodServer = false,
+ ) {
+ composeTestRule.onEditButton().assertDoesNotExist()
+ }
+
+ @Test
+ fun `When single PM with remove permissions & cannot remove last PM from config, edit should not be shown`() =
+ runTest(
+ cards = listOf(
+ PaymentMethodFactory.card(last4 = "4242"),
+ ),
+ isPaymentMethodRemoveEnabled = true,
+ canRemoveLastPaymentMethodConfig = false,
+ canRemoveLastPaymentMethodServer = true,
+ ) {
+ composeTestRule.onEditButton().assertDoesNotExist()
+ }
+
@Test
fun `When single PM with remove permissions but cannot remove last PM, edit button should not be displayed`() =
runTest(
@@ -87,7 +115,8 @@ internal class CustomerSessionPaymentSheetActivityTest {
PaymentMethodFactory.card(last4 = "4242"),
),
isPaymentMethodRemoveEnabled = true,
- allowsRemovalOfLastSavedPaymentMethod = false,
+ canRemoveLastPaymentMethodConfig = false,
+ canRemoveLastPaymentMethodServer = false,
) {
composeTestRule.onEditButton().assertDoesNotExist()
}
@@ -100,7 +129,8 @@ internal class CustomerSessionPaymentSheetActivityTest {
PaymentMethodFactory.card(last4 = "5544"),
),
isPaymentMethodRemoveEnabled = false,
- allowsRemovalOfLastSavedPaymentMethod = true,
+ canRemoveLastPaymentMethodConfig = true,
+ canRemoveLastPaymentMethodServer = false,
) {
composeTestRule.onEditButton().assertDoesNotExist()
}
@@ -113,7 +143,8 @@ internal class CustomerSessionPaymentSheetActivityTest {
PaymentMethodFactory.card(last4 = "5544"),
),
isPaymentMethodRemoveEnabled = false,
- allowsRemovalOfLastSavedPaymentMethod = true,
+ canRemoveLastPaymentMethodConfig = true,
+ canRemoveLastPaymentMethodServer = false,
) {
composeTestRule.onEditButton().performClick()
@@ -133,7 +164,8 @@ internal class CustomerSessionPaymentSheetActivityTest {
PaymentMethodFactory.card(last4 = "5544"),
),
isPaymentMethodRemoveEnabled = true,
- allowsRemovalOfLastSavedPaymentMethod = true,
+ canRemoveLastPaymentMethodConfig = true,
+ canRemoveLastPaymentMethodServer = false,
) {
composeTestRule.onEditButton().performClick()
@@ -150,13 +182,58 @@ internal class CustomerSessionPaymentSheetActivityTest {
}
@Test
- fun `When single CBC card, has remove permissions, and can remove last PM, should be able to remove and edit`() =
+ fun `When single CBC card, has remove permissions, and cannot remove last PM from server, can only edit`() =
runTest(
cards = listOf(
PaymentMethodFactory.card(last4 = "4242", addCbcNetworks = true),
),
isPaymentMethodRemoveEnabled = true,
- allowsRemovalOfLastSavedPaymentMethod = true,
+ canRemoveLastPaymentMethodConfig = true,
+ canRemoveLastPaymentMethodServer = false,
+ ) {
+ composeTestRule.onEditButton().performClick()
+
+ val cbcCard = composeTestRule.onSavedPaymentMethod(last4 = "4242")
+
+ cbcCard.assertIsEnabled()
+ cbcCard.assertHasModifyBadge()
+
+ composeTestRule.onModifyBadgeFor(last4 = "4242").performClick()
+
+ composeTestRule.onEditScreenRemoveButton().assertDoesNotExist()
+ }
+
+ @Test
+ fun `When single CBC card, has remove permissions, and cannot remove last PM from config, can only edit`() =
+ runTest(
+ cards = listOf(
+ PaymentMethodFactory.card(last4 = "4242", addCbcNetworks = true),
+ ),
+ isPaymentMethodRemoveEnabled = true,
+ canRemoveLastPaymentMethodConfig = false,
+ canRemoveLastPaymentMethodServer = true,
+ ) {
+ composeTestRule.onEditButton().performClick()
+
+ val cbcCard = composeTestRule.onSavedPaymentMethod(last4 = "4242")
+
+ cbcCard.assertIsEnabled()
+ cbcCard.assertHasModifyBadge()
+
+ composeTestRule.onModifyBadgeFor(last4 = "4242").performClick()
+
+ composeTestRule.onEditScreenRemoveButton().assertDoesNotExist()
+ }
+
+ @Test
+ fun `When single CBC card, has remove permissions, and can remove last PM from all sources, can remove and edit`() =
+ runTest(
+ cards = listOf(
+ PaymentMethodFactory.card(last4 = "4242", addCbcNetworks = true),
+ ),
+ isPaymentMethodRemoveEnabled = true,
+ canRemoveLastPaymentMethodConfig = true,
+ canRemoveLastPaymentMethodServer = true,
) {
composeTestRule.onEditButton().performClick()
@@ -177,7 +254,8 @@ internal class CustomerSessionPaymentSheetActivityTest {
PaymentMethodFactory.card(last4 = "4242", addCbcNetworks = true),
),
isPaymentMethodRemoveEnabled = false,
- allowsRemovalOfLastSavedPaymentMethod = true,
+ canRemoveLastPaymentMethodConfig = true,
+ canRemoveLastPaymentMethodServer = false,
) {
composeTestRule.onEditButton().performClick()
@@ -198,7 +276,8 @@ internal class CustomerSessionPaymentSheetActivityTest {
PaymentMethodFactory.card(last4 = "4242", addCbcNetworks = true),
),
isPaymentMethodRemoveEnabled = true,
- allowsRemovalOfLastSavedPaymentMethod = false,
+ canRemoveLastPaymentMethodConfig = false,
+ canRemoveLastPaymentMethodServer = false,
) {
composeTestRule.onEditButton().performClick()
@@ -215,7 +294,8 @@ internal class CustomerSessionPaymentSheetActivityTest {
private fun runTest(
cards: List,
isPaymentMethodRemoveEnabled: Boolean,
- allowsRemovalOfLastSavedPaymentMethod: Boolean,
+ canRemoveLastPaymentMethodConfig: Boolean,
+ canRemoveLastPaymentMethodServer: Boolean,
test: (PaymentSheetActivity) -> Unit,
) {
networkRule.enqueue(
@@ -223,7 +303,13 @@ internal class CustomerSessionPaymentSheetActivityTest {
RequestMatchers.method("GET"),
RequestMatchers.path("/v1/elements/sessions"),
) { response ->
- response.setBody(createElementsSessionResponse(cards, isPaymentMethodRemoveEnabled))
+ response.setBody(
+ createElementsSessionResponse(
+ cards = cards,
+ isPaymentMethodRemoveEnabled = isPaymentMethodRemoveEnabled,
+ canRemoveLastPaymentMethod = canRemoveLastPaymentMethodServer,
+ )
+ )
}
val countDownLatch = CountDownLatch(1)
@@ -241,7 +327,7 @@ internal class CustomerSessionPaymentSheetActivityTest {
id = "cus_1",
clientSecret = "cuss_1",
),
- allowsRemovalOfLastSavedPaymentMethod = allowsRemovalOfLastSavedPaymentMethod,
+ allowsRemovalOfLastSavedPaymentMethod = canRemoveLastPaymentMethodConfig,
preferredNetworks = listOf(CardBrand.CartesBancaires, CardBrand.Visa),
paymentMethodLayout = PaymentSheet.PaymentMethodLayout.Horizontal,
),
@@ -302,6 +388,7 @@ internal class CustomerSessionPaymentSheetActivityTest {
fun createElementsSessionResponse(
cards: List,
isPaymentMethodRemoveEnabled: Boolean,
+ canRemoveLastPaymentMethod: Boolean,
): String {
val cardsArray = JSONArray()
@@ -317,6 +404,12 @@ internal class CustomerSessionPaymentSheetActivityTest {
"disabled"
}
+ val canRemoveLastPaymentMethodStringified = if (canRemoveLastPaymentMethod) {
+ "enabled"
+ } else {
+ "disabled"
+ }
+
return """
{
"business_name": "Mobile Example Account",
@@ -347,6 +440,7 @@ internal class CustomerSessionPaymentSheetActivityTest {
"features": {
"payment_method_save": "enabled",
"payment_method_remove": "$isPaymentMethodRemoveStringified",
+ "payment_method_remove_last": "$canRemoveLastPaymentMethodStringified",
"payment_method_save_allow_redisplay_override": null
}
},
diff --git a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/CustomerStateHolderTest.kt b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/CustomerStateHolderTest.kt
index 04c8856cc7b..d2d1e685e9f 100644
--- a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/CustomerStateHolderTest.kt
+++ b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/CustomerStateHolderTest.kt
@@ -3,6 +3,7 @@ package com.stripe.android.paymentsheet
import androidx.lifecycle.SavedStateHandle
import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
+import com.stripe.android.common.model.asCommonConfiguration
import com.stripe.android.model.PaymentMethodFixtures
import com.stripe.android.paymentsheet.model.PaymentSelection
import com.stripe.android.paymentsheet.state.CustomerState
@@ -31,6 +32,7 @@ internal class CustomerStateHolderTest {
val savedStateHandle = SavedStateHandle()
val customerState = CustomerState.createForLegacyEphemeralKey(
customerId = "cus_123",
+ configuration = PaymentSheetFixtures.CONFIG_CUSTOMER.asCommonConfiguration(),
accessType = PaymentSheet.CustomerAccessType.LegacyCustomerEphemeralKey("ek_123"),
paymentMethods = emptyList()
)
@@ -50,6 +52,7 @@ internal class CustomerStateHolderTest {
customerStateHolder.setCustomerState(
CustomerState.createForLegacyEphemeralKey(
customerId = "cus_123",
+ configuration = PaymentSheetFixtures.CONFIG_CUSTOMER.asCommonConfiguration(),
accessType = PaymentSheet.CustomerAccessType.LegacyCustomerEphemeralKey("ek_123"),
paymentMethods = listOf(PaymentMethodFixtures.CARD_PAYMENT_METHOD)
)
diff --git a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/PaymentSheetFixtures.kt b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/PaymentSheetFixtures.kt
index 3b187822d06..701622333c3 100644
--- a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/PaymentSheetFixtures.kt
+++ b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/PaymentSheetFixtures.kt
@@ -97,6 +97,7 @@ internal object PaymentSheetFixtures {
paymentMethods = listOf(),
permissions = CustomerState.Permissions(
canRemovePaymentMethods = true,
+ canRemoveLastPaymentMethod = true,
canRemoveDuplicates = false,
)
)
@@ -159,6 +160,7 @@ internal object PaymentSheetFixtures {
paymentMethods = paymentMethods,
permissions = CustomerState.Permissions(
canRemovePaymentMethods = true,
+ canRemoveLastPaymentMethod = true,
canRemoveDuplicates = false,
)
),
diff --git a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/PaymentSheetViewModelTest.kt b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/PaymentSheetViewModelTest.kt
index 89b1507c6df..d5df7baa324 100644
--- a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/PaymentSheetViewModelTest.kt
+++ b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/PaymentSheetViewModelTest.kt
@@ -334,6 +334,7 @@ internal class PaymentSheetViewModelTest {
paymentMethods = paymentMethods,
permissions = CustomerState.Permissions(
canRemovePaymentMethods = true,
+ canRemoveLastPaymentMethod = true,
canRemoveDuplicates = false,
),
),
diff --git a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/SavedPaymentMethodMutatorTest.kt b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/SavedPaymentMethodMutatorTest.kt
index 23224ac0d47..b093a60e9b0 100644
--- a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/SavedPaymentMethodMutatorTest.kt
+++ b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/SavedPaymentMethodMutatorTest.kt
@@ -30,16 +30,14 @@ import org.mockito.Mockito.mock
class SavedPaymentMethodMutatorTest {
@Test
- fun `canRemove is correct when no payment methods for customer`() = runScenario(
- allowsRemovalOfLastSavedPaymentMethod = true,
- ) {
+ fun `canRemove is correct when no payment methods for customer`() = runScenario {
savedPaymentMethodMutator.canRemove.test {
assertThat(awaitItem()).isFalse()
customerStateHolder.setCustomerState(
- CustomerState.createForLegacyEphemeralKey(
- customerId = "cus_123",
- accessType = PaymentSheet.CustomerAccessType.LegacyCustomerEphemeralKey("ek_123"),
+ createCustomerState(
+ isRemoveEnabled = true,
+ canRemoveLastPaymentMethod = true,
paymentMethods = listOf()
)
)
@@ -50,19 +48,15 @@ class SavedPaymentMethodMutatorTest {
}
@Test
- fun `canRemove is correct when one payment method & allowsRemovalOfLastSavedPaymentMethod is true`() =
- runScenario(
- allowsRemovalOfLastSavedPaymentMethod = true,
- ) {
+ fun `canRemove is correct when one payment method & can remove last payment method`() =
+ runScenario {
savedPaymentMethodMutator.canRemove.test {
assertThat(awaitItem()).isFalse()
customerStateHolder.setCustomerState(
- CustomerState.createForLegacyEphemeralKey(
- customerId = "cus_123",
- accessType = PaymentSheet
- .CustomerAccessType
- .LegacyCustomerEphemeralKey("ek_123"),
+ createCustomerState(
+ isRemoveEnabled = true,
+ canRemoveLastPaymentMethod = true,
paymentMethods = PaymentMethodFactory.cards(1),
)
)
@@ -74,19 +68,15 @@ class SavedPaymentMethodMutatorTest {
}
@Test
- fun `canRemove is correct when one payment method & allowsRemovalOfLastSavedPaymentMethod is false`() =
- runScenario(
- allowsRemovalOfLastSavedPaymentMethod = false,
- ) {
+ fun `canRemove is correct when one payment method & cannot remove last payment method`() =
+ runScenario {
savedPaymentMethodMutator.canRemove.test {
assertThat(awaitItem()).isFalse()
customerStateHolder.setCustomerState(
- CustomerState.createForLegacyEphemeralKey(
- customerId = "cus_123",
- accessType = PaymentSheet
- .CustomerAccessType
- .LegacyCustomerEphemeralKey("ek_123"),
+ createCustomerState(
+ isRemoveEnabled = true,
+ canRemoveLastPaymentMethod = false,
paymentMethods = PaymentMethodFactory.cards(1),
)
)
@@ -97,19 +87,15 @@ class SavedPaymentMethodMutatorTest {
}
@Test
- fun `canRemove is correct when multiple payment methods & allowsRemovalOfLastSavedPaymentMethod is true`() =
- runScenario(
- allowsRemovalOfLastSavedPaymentMethod = true,
- ) {
+ fun `canRemove is correct when multiple payment methods & can remove last payment method`() =
+ runScenario {
savedPaymentMethodMutator.canRemove.test {
assertThat(awaitItem()).isFalse()
customerStateHolder.setCustomerState(
- CustomerState.createForLegacyEphemeralKey(
- customerId = "cus_123",
- accessType = PaymentSheet
- .CustomerAccessType
- .LegacyCustomerEphemeralKey("ek_123"),
+ createCustomerState(
+ isRemoveEnabled = true,
+ canRemoveLastPaymentMethod = true,
paymentMethods = PaymentMethodFactory.cards(2),
)
)
@@ -119,19 +105,15 @@ class SavedPaymentMethodMutatorTest {
}
@Test
- fun `canRemove is correct when multiple payment methods & allowsRemovalOfLastSavedPaymentMethod is false`() =
- runScenario(
- allowsRemovalOfLastSavedPaymentMethod = false,
- ) {
+ fun `canRemove is correct when multiple payment methods & cannot remove last payment method`() =
+ runScenario {
savedPaymentMethodMutator.canRemove.test {
assertThat(awaitItem()).isFalse()
customerStateHolder.setCustomerState(
- CustomerState.createForLegacyEphemeralKey(
- customerId = "cus_123",
- accessType = PaymentSheet
- .CustomerAccessType
- .LegacyCustomerEphemeralKey("ek_123"),
+ createCustomerState(
+ isRemoveEnabled = true,
+ canRemoveLastPaymentMethod = false,
paymentMethods = PaymentMethodFactory.cards(2),
)
)
@@ -141,10 +123,8 @@ class SavedPaymentMethodMutatorTest {
}
@Test
- fun `canRemove is correct when has remove permissions & allowsRemovalOfLastSavedPaymentMethod is true`() =
- runScenario(
- allowsRemovalOfLastSavedPaymentMethod = true,
- ) {
+ fun `canRemove is correct when has remove permissions & can remove last payment method`() =
+ runScenario {
savedPaymentMethodMutator.canRemove.test {
assertThat(awaitItem()).isFalse()
@@ -152,6 +132,7 @@ class SavedPaymentMethodMutatorTest {
createCustomerState(
paymentMethods = PaymentMethodFactory.cards(1),
isRemoveEnabled = true,
+ canRemoveLastPaymentMethod = true,
)
)
@@ -162,10 +143,8 @@ class SavedPaymentMethodMutatorTest {
}
@Test
- fun `canRemove is correct when has remove permissions & allowsRemovalOfLastSavedPaymentMethod is false`() =
- runScenario(
- allowsRemovalOfLastSavedPaymentMethod = false,
- ) {
+ fun `canRemove is correct when has remove permissions & canRemoveLastPaymentMethod is false`() =
+ runScenario {
savedPaymentMethodMutator.canRemove.test {
assertThat(awaitItem()).isFalse()
@@ -173,6 +152,7 @@ class SavedPaymentMethodMutatorTest {
createCustomerState(
paymentMethods = PaymentMethodFactory.cards(1),
isRemoveEnabled = true,
+ canRemoveLastPaymentMethod = false,
)
)
@@ -181,10 +161,8 @@ class SavedPaymentMethodMutatorTest {
}
@Test
- fun `canRemove is correct when does not remove permissions & allowsRemovalOfLastSavedPaymentMethod is true`() =
- runScenario(
- allowsRemovalOfLastSavedPaymentMethod = true,
- ) {
+ fun `canRemove is correct when does not remove permissions & canRemoveLastPaymentMethod is true`() =
+ runScenario {
savedPaymentMethodMutator.canRemove.test {
assertThat(awaitItem()).isFalse()
@@ -192,6 +170,7 @@ class SavedPaymentMethodMutatorTest {
createCustomerState(
paymentMethods = PaymentMethodFactory.cards(1),
isRemoveEnabled = false,
+ canRemoveLastPaymentMethod = true,
)
)
@@ -200,18 +179,14 @@ class SavedPaymentMethodMutatorTest {
}
@Test
- fun `canEdit is correct when no payment methods`() = runScenario(
- allowsRemovalOfLastSavedPaymentMethod = true,
- ) {
+ fun `canEdit is correct when no payment methods`() = runScenario {
savedPaymentMethodMutator.canEdit.test {
assertThat(awaitItem()).isFalse()
customerStateHolder.setCustomerState(
- CustomerState.createForLegacyEphemeralKey(
- customerId = "cus_123",
- accessType = PaymentSheet
- .CustomerAccessType
- .LegacyCustomerEphemeralKey("ek_123"),
+ createCustomerState(
+ isRemoveEnabled = true,
+ canRemoveLastPaymentMethod = true,
paymentMethods = listOf()
)
)
@@ -222,16 +197,14 @@ class SavedPaymentMethodMutatorTest {
}
@Test
- fun `canEdit is correct when allowsRemovalOfLastSavedPaymentMethod is true`() = runScenario(
- allowsRemovalOfLastSavedPaymentMethod = true,
- ) {
+ fun `canEdit is correct when user has permissions to remove last PM`() = runScenario {
savedPaymentMethodMutator.canEdit.test {
assertThat(awaitItem()).isFalse()
customerStateHolder.setCustomerState(
- CustomerState.createForLegacyEphemeralKey(
- customerId = "cus_123",
- accessType = PaymentSheet.CustomerAccessType.LegacyCustomerEphemeralKey("ek_123"),
+ createCustomerState(
+ isRemoveEnabled = true,
+ canRemoveLastPaymentMethod = true,
paymentMethods = listOf(PaymentMethodFixtures.CARD_PAYMENT_METHOD)
)
)
@@ -240,16 +213,14 @@ class SavedPaymentMethodMutatorTest {
}
@Test
- fun `canEdit is correct when allowsRemovalOfLastSavedPaymentMethod is false`() = runScenario(
- allowsRemovalOfLastSavedPaymentMethod = false,
- ) {
+ fun `canEdit is correct when when user does not have permissions to remove last PM`() = runScenario {
savedPaymentMethodMutator.canEdit.test {
assertThat(awaitItem()).isFalse()
customerStateHolder.setCustomerState(
- CustomerState.createForLegacyEphemeralKey(
- customerId = "cus_123",
- accessType = PaymentSheet.CustomerAccessType.LegacyCustomerEphemeralKey("ek_123"),
+ createCustomerState(
+ isRemoveEnabled = true,
+ canRemoveLastPaymentMethod = false,
paymentMethods = listOf(
PaymentMethodFixtures.CARD_PAYMENT_METHOD,
PaymentMethodFixtures.CARD_WITH_NETWORKS_PAYMENT_METHOD
@@ -259,9 +230,9 @@ class SavedPaymentMethodMutatorTest {
assertThat(awaitItem()).isTrue()
customerStateHolder.setCustomerState(
- CustomerState.createForLegacyEphemeralKey(
- customerId = "cus_123",
- accessType = PaymentSheet.CustomerAccessType.LegacyCustomerEphemeralKey("ek_123"),
+ createCustomerState(
+ isRemoveEnabled = true,
+ canRemoveLastPaymentMethod = false,
paymentMethods = listOf(
PaymentMethodFixtures.CARD_WITH_NETWORKS_PAYMENT_METHOD,
)
@@ -273,16 +244,15 @@ class SavedPaymentMethodMutatorTest {
@Test
fun `canEdit is correct CBC is enabled`() = runScenario(
- allowsRemovalOfLastSavedPaymentMethod = false,
isCbcEligible = { true }
) {
savedPaymentMethodMutator.canEdit.test {
assertThat(awaitItem()).isFalse()
customerStateHolder.setCustomerState(
- CustomerState.createForLegacyEphemeralKey(
- customerId = "cus_123",
- accessType = PaymentSheet.CustomerAccessType.LegacyCustomerEphemeralKey("ek_123"),
+ createCustomerState(
+ isRemoveEnabled = true,
+ canRemoveLastPaymentMethod = false,
paymentMethods = listOf(
PaymentMethodFixtures.CARD_WITH_NETWORKS_PAYMENT_METHOD,
)
@@ -294,12 +264,12 @@ class SavedPaymentMethodMutatorTest {
assertThat(awaitItem()).isFalse()
customerStateHolder.setCustomerState(
- CustomerState.createForLegacyEphemeralKey(
- customerId = "cus_123",
- accessType = PaymentSheet.CustomerAccessType.LegacyCustomerEphemeralKey("ek_123"),
+ createCustomerState(
+ isRemoveEnabled = true,
+ canRemoveLastPaymentMethod = false,
paymentMethods = listOf(
PaymentMethodFixtures.CARD_WITH_NETWORKS_PAYMENT_METHOD
- )
+ ),
)
)
assertThat(awaitItem()).isTrue()
@@ -319,12 +289,12 @@ class SavedPaymentMethodMutatorTest {
runScenario(customerRepository = customerRepository) {
customerStateHolder.setCustomerState(
- CustomerState.createForLegacyEphemeralKey(
- customerId = "cus_123",
- accessType = PaymentSheet.CustomerAccessType.LegacyCustomerEphemeralKey("ek_123"),
+ createCustomerState(
+ isRemoveEnabled = true,
+ canRemoveLastPaymentMethod = true,
paymentMethods = listOf(
PaymentMethodFixtures.CARD_PAYMENT_METHOD
- )
+ ),
)
)
@@ -383,10 +353,51 @@ class SavedPaymentMethodMutatorTest {
createCustomerState(
paymentMethods = cards,
isRemoveEnabled = true,
+ canRemoveLastPaymentMethod = true,
+ )
+ )
+
+ savedPaymentMethodMutator.modifyPaymentMethod(cards[0])
+ modifyPaymentMethodTurbine.awaitItem().apply {
+ assertThat(paymentMethod).isEqualTo(cards[0])
+ assertThat(canRemove).isTrue()
+ }
+ }
+
+ @Test
+ fun `modifyPaymentMethod should be called correctly when 1 PM & cannot remove last PM`() = runScenario {
+ val cards = PaymentMethodFixtures.createCards(1)
+
+ customerStateHolder.setCustomerState(
+ createCustomerState(
+ paymentMethods = cards,
+ isRemoveEnabled = true,
+ canRemoveLastPaymentMethod = false,
+ )
+ )
+
+ savedPaymentMethodMutator.modifyPaymentMethod(cards[0])
+
+ modifyPaymentMethodTurbine.awaitItem().apply {
+ assertThat(paymentMethod).isEqualTo(cards[0])
+ assertThat(canRemove).isFalse()
+ }
+ }
+
+ @Test
+ fun `modifyPaymentMethod be called correctly when multiple PMs & cannot remove last PM`() = runScenario {
+ val cards = PaymentMethodFixtures.createCards(2)
+
+ customerStateHolder.setCustomerState(
+ createCustomerState(
+ paymentMethods = cards,
+ isRemoveEnabled = true,
+ canRemoveLastPaymentMethod = false,
)
)
savedPaymentMethodMutator.modifyPaymentMethod(cards[0])
+
modifyPaymentMethodTurbine.awaitItem().apply {
assertThat(paymentMethod).isEqualTo(cards[0])
assertThat(canRemove).isTrue()
@@ -450,6 +461,7 @@ class SavedPaymentMethodMutatorTest {
createCustomerState(
paymentMethods = listOf(displayableSavedPaymentMethod.paymentMethod),
isRemoveEnabled = true,
+ canRemoveLastPaymentMethod = true,
)
)
@@ -484,6 +496,7 @@ class SavedPaymentMethodMutatorTest {
createCustomerState(
paymentMethods = listOf(displayableSavedPaymentMethod.paymentMethod),
isRemoveEnabled = true,
+ canRemoveLastPaymentMethod = true,
)
)
@@ -519,6 +532,7 @@ class SavedPaymentMethodMutatorTest {
createCustomerState(
paymentMethods = listOf(displayableSavedPaymentMethod.paymentMethod),
isRemoveEnabled = true,
+ canRemoveLastPaymentMethod = true,
)
)
@@ -554,6 +568,7 @@ class SavedPaymentMethodMutatorTest {
createCustomerState(
paymentMethods = listOf(displayableSavedPaymentMethod.paymentMethod),
isRemoveEnabled = true,
+ canRemoveLastPaymentMethod = true,
)
)
@@ -589,6 +604,7 @@ class SavedPaymentMethodMutatorTest {
createCustomerState(
paymentMethods = listOf(displayableSavedPaymentMethod.paymentMethod),
isRemoveEnabled = true,
+ canRemoveLastPaymentMethod = true,
)
)
@@ -623,6 +639,7 @@ class SavedPaymentMethodMutatorTest {
createCustomerState(
paymentMethods = listOf(displayableSavedPaymentMethod.paymentMethod),
isRemoveEnabled = true,
+ canRemoveLastPaymentMethod = true,
)
)
@@ -658,6 +675,7 @@ class SavedPaymentMethodMutatorTest {
createCustomerState(
paymentMethods = listOf(displayableSavedPaymentMethod.paymentMethod),
isRemoveEnabled = true,
+ canRemoveLastPaymentMethod = true,
)
)
@@ -693,6 +711,7 @@ class SavedPaymentMethodMutatorTest {
createCustomerState(
paymentMethods = listOf(displayableSavedPaymentMethod.paymentMethod),
isRemoveEnabled = true,
+ canRemoveLastPaymentMethod = true,
)
)
@@ -714,6 +733,7 @@ class SavedPaymentMethodMutatorTest {
private fun createCustomerState(
paymentMethods: List,
isRemoveEnabled: Boolean,
+ canRemoveLastPaymentMethod: Boolean,
): CustomerState {
return CustomerState(
id = "cus_1",
@@ -722,6 +742,7 @@ class SavedPaymentMethodMutatorTest {
permissions = CustomerState.Permissions(
canRemovePaymentMethods = isRemoveEnabled,
canRemoveDuplicates = true,
+ canRemoveLastPaymentMethod = canRemoveLastPaymentMethod,
)
)
}
@@ -737,6 +758,7 @@ class SavedPaymentMethodMutatorTest {
paymentMethods = listOf(),
permissions = CustomerState.Permissions(
canRemovePaymentMethods = true,
+ canRemoveLastPaymentMethod = true,
canRemoveDuplicates = shouldRemoveDuplicates,
)
)
@@ -763,7 +785,6 @@ class SavedPaymentMethodMutatorTest {
private fun runScenario(
customerRepository: CustomerRepository = FakeCustomerRepository(),
- allowsRemovalOfLastSavedPaymentMethod: Boolean = true,
isCbcEligible: () -> Boolean = { false },
block: suspend Scenario.() -> Unit
) {
@@ -786,7 +807,6 @@ class SavedPaymentMethodMutatorTest {
coroutineScope = CoroutineScope(UnconfinedTestDispatcher()),
workContext = coroutineContext,
customerRepository = customerRepository,
- allowsRemovalOfLastSavedPaymentMethod = allowsRemovalOfLastSavedPaymentMethod,
selection = selection,
providePaymentMethodName = { it?.resolvableString.orEmpty() },
customerStateHolder = customerStateHolder,
diff --git a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/state/CustomerStateTest.kt b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/state/CustomerStateTest.kt
index f9de305c826..58d1abd0603 100644
--- a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/state/CustomerStateTest.kt
+++ b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/state/CustomerStateTest.kt
@@ -1,10 +1,13 @@
package com.stripe.android.paymentsheet.state
import com.google.common.truth.Truth.assertThat
+import com.stripe.android.common.model.CommonConfiguration
+import com.stripe.android.common.model.asCommonConfiguration
import com.stripe.android.model.ElementsSession
import com.stripe.android.model.PaymentMethod
import com.stripe.android.model.PaymentMethodFixtures
import com.stripe.android.paymentsheet.PaymentSheet
+import com.stripe.android.paymentsheet.PaymentSheetFixtures
import com.stripe.android.testing.PaymentMethodFactory
import kotlinx.coroutines.test.runTest
import org.junit.Test
@@ -22,6 +25,7 @@ class CustomerStateTest {
val customerState = CustomerState.createForCustomerSession(
customer = customer,
+ configuration = createConfiguration(),
supportedSavedPaymentMethodTypes = listOf(PaymentMethod.Type.Card)
)
@@ -32,6 +36,7 @@ class CustomerStateTest {
paymentMethods = paymentMethods,
permissions = CustomerState.Permissions(
canRemovePaymentMethods = false,
+ canRemoveLastPaymentMethod = false,
// Always true for `customer_session`
canRemoveDuplicates = true,
),
@@ -49,12 +54,14 @@ class CustomerStateTest {
mobilePaymentElementComponent = ElementsSession.Customer.Components.MobilePaymentElement.Enabled(
isPaymentMethodSaveEnabled = false,
isPaymentMethodRemoveEnabled = true,
+ canRemoveLastPaymentMethod = true,
allowRedisplayOverride = null,
),
)
val customerState = CustomerState.createForCustomerSession(
customer = customer,
+ configuration = createConfiguration(),
supportedSavedPaymentMethodTypes = listOf(PaymentMethod.Type.Card)
)
@@ -65,6 +72,7 @@ class CustomerStateTest {
paymentMethods = paymentMethods,
permissions = CustomerState.Permissions(
canRemovePaymentMethods = true,
+ canRemoveLastPaymentMethod = true,
// Always true for `customer_session`
canRemoveDuplicates = true,
),
@@ -82,12 +90,14 @@ class CustomerStateTest {
mobilePaymentElementComponent = ElementsSession.Customer.Components.MobilePaymentElement.Enabled(
isPaymentMethodSaveEnabled = false,
isPaymentMethodRemoveEnabled = false,
+ canRemoveLastPaymentMethod = false,
allowRedisplayOverride = null,
),
)
val customerState = CustomerState.createForCustomerSession(
customer = customer,
+ configuration = createConfiguration(),
supportedSavedPaymentMethodTypes = listOf(PaymentMethod.Type.Card)
)
@@ -98,6 +108,7 @@ class CustomerStateTest {
paymentMethods = paymentMethods,
permissions = CustomerState.Permissions(
canRemovePaymentMethods = false,
+ canRemoveLastPaymentMethod = false,
// Always true for `customer_session`
canRemoveDuplicates = true,
),
@@ -113,6 +124,7 @@ class CustomerStateTest {
accessType = PaymentSheet.CustomerAccessType.LegacyCustomerEphemeralKey(
ephemeralKeySecret = "ek_1",
),
+ configuration = createConfiguration(),
paymentMethods = paymentMethods,
)
@@ -124,6 +136,8 @@ class CustomerStateTest {
permissions = CustomerState.Permissions(
// Always true for legacy ephemeral keys since un-scoped
canRemovePaymentMethods = true,
+ // Always true unless configured client-side
+ canRemoveLastPaymentMethod = true,
// Always 'false' for legacy ephemeral keys
canRemoveDuplicates = false,
),
@@ -145,22 +159,134 @@ class CustomerStateTest {
mobilePaymentElementComponent = ElementsSession.Customer.Components.MobilePaymentElement.Enabled(
isPaymentMethodSaveEnabled = false,
isPaymentMethodRemoveEnabled = false,
+ canRemoveLastPaymentMethod = false,
allowRedisplayOverride = null,
),
)
val customerState = CustomerState.createForCustomerSession(
customer = customer,
+ configuration = createConfiguration(),
supportedSavedPaymentMethodTypes = listOf(PaymentMethod.Type.Card)
)
assertThat(customerState.paymentMethods).containsExactlyElementsIn(cards)
}
+ @Test
+ fun `Should set 'canRemoveLastPaymentMethod' to true if config value is true for legacy ephemeral keys`() {
+ val customerState = CustomerState.createForLegacyEphemeralKey(
+ customerId = "cus_1",
+ accessType = PaymentSheet.CustomerAccessType.LegacyCustomerEphemeralKey(
+ ephemeralKeySecret = "ek_1",
+ ),
+ configuration = createConfiguration(allowsRemovalOfLastSavedPaymentMethod = true),
+ paymentMethods = listOf(),
+ )
+
+ assertThat(customerState.permissions.canRemoveLastPaymentMethod).isTrue()
+ }
+
+ @Test
+ fun `Should set 'canRemoveLastPaymentMethod' to false if config value is false for legacy ephemeral keys`() {
+ val customerState = CustomerState.createForLegacyEphemeralKey(
+ customerId = "cus_1",
+ accessType = PaymentSheet.CustomerAccessType.LegacyCustomerEphemeralKey(
+ ephemeralKeySecret = "ek_1",
+ ),
+ configuration = createConfiguration(allowsRemovalOfLastSavedPaymentMethod = false),
+ paymentMethods = listOf(),
+ )
+
+ assertThat(customerState.permissions.canRemoveLastPaymentMethod).isFalse()
+ }
+
+ @Test
+ fun `Should set 'canRemoveLastPaymentMethod' to false if config value & server value are false`() =
+ customerSessionPermissionsTest(
+ canRemoveLastPaymentMethod = false,
+ canRemoveLastPaymentMethodConfigValue = false,
+ ) { customerState ->
+ assertThat(customerState.permissions.canRemoveLastPaymentMethod).isFalse()
+ }
+
+ @Test
+ fun `Should set 'canRemoveLastPaymentMethod' to false if config value is false & server MPE is disabled`() =
+ customerSessionPermissionsTest(
+ paymentElementDisabled = false,
+ canRemoveLastPaymentMethodConfigValue = false,
+ ) { customerState ->
+ assertThat(customerState.permissions.canRemoveLastPaymentMethod).isFalse()
+ }
+
+ @Test
+ fun `Should set 'canRemoveLastPaymentMethod' to false if config value is true but server MPE is disabled`() =
+ customerSessionPermissionsTest(
+ paymentElementDisabled = true,
+ canRemoveLastPaymentMethodConfigValue = true,
+ ) { customerState ->
+ assertThat(customerState.permissions.canRemoveLastPaymentMethod).isFalse()
+ }
+
+ @Test
+ fun `Should set 'canRemoveLastPaymentMethod' to false if config value is true but server value is false`() =
+ customerSessionPermissionsTest(
+ canRemoveLastPaymentMethod = false,
+ canRemoveLastPaymentMethodConfigValue = true,
+ ) { customerState ->
+ assertThat(customerState.permissions.canRemoveLastPaymentMethod).isFalse()
+ }
+
+ @Test
+ fun `Should set 'canRemoveLastPaymentMethod' to false if config value is false but server value is true`() =
+ customerSessionPermissionsTest(
+ canRemoveLastPaymentMethod = true,
+ canRemoveLastPaymentMethodConfigValue = false,
+ ) { customerState ->
+ assertThat(customerState.permissions.canRemoveLastPaymentMethod).isFalse()
+ }
+
+ @Test
+ fun `Should set 'canRemoveLastPaymentMethod' to true if config value & server value are true`() =
+ customerSessionPermissionsTest(
+ canRemoveLastPaymentMethod = true,
+ canRemoveLastPaymentMethodConfigValue = true,
+ ) { customerState ->
+ assertThat(customerState.permissions.canRemoveLastPaymentMethod).isTrue()
+ }
+
+ private fun customerSessionPermissionsTest(
+ paymentElementDisabled: Boolean = false,
+ canRemoveLastPaymentMethodConfigValue: Boolean = true,
+ canRemoveLastPaymentMethod: Boolean = true,
+ test: (customerState: CustomerState) -> Unit,
+ ) {
+ val customerState = CustomerState.createForCustomerSession(
+ customer = createElementsSessionCustomer(
+ mobilePaymentElementComponent = if (paymentElementDisabled) {
+ ElementsSession.Customer.Components.MobilePaymentElement.Disabled
+ } else {
+ ElementsSession.Customer.Components.MobilePaymentElement.Enabled(
+ isPaymentMethodRemoveEnabled = true,
+ isPaymentMethodSaveEnabled = false,
+ canRemoveLastPaymentMethod = canRemoveLastPaymentMethod,
+ allowRedisplayOverride = null,
+ )
+ }
+ ),
+ configuration = createConfiguration(
+ allowsRemovalOfLastSavedPaymentMethod = canRemoveLastPaymentMethodConfigValue
+ ),
+ supportedSavedPaymentMethodTypes = listOf(PaymentMethod.Type.Card)
+ )
+
+ test(customerState)
+ }
+
private fun createElementsSessionCustomer(
customerId: String = "cus_1",
ephemeralKeySecret: String = "ek_1",
- paymentMethods: List,
+ paymentMethods: List = listOf(),
mobilePaymentElementComponent: ElementsSession.Customer.Components.MobilePaymentElement
): ElementsSession.Customer {
return ElementsSession.Customer(
@@ -179,4 +305,12 @@ class CustomerStateTest {
),
)
}
+
+ private fun createConfiguration(
+ allowsRemovalOfLastSavedPaymentMethod: Boolean = true,
+ ): CommonConfiguration {
+ return PaymentSheetFixtures.CONFIG_CUSTOMER.asCommonConfiguration().copy(
+ allowsRemovalOfLastSavedPaymentMethod = allowsRemovalOfLastSavedPaymentMethod,
+ )
+ }
}
diff --git a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/state/DefaultPaymentElementLoaderTest.kt b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/state/DefaultPaymentElementLoaderTest.kt
index deec2e92fa8..80724ba3816 100644
--- a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/state/DefaultPaymentElementLoaderTest.kt
+++ b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/state/DefaultPaymentElementLoaderTest.kt
@@ -127,6 +127,7 @@ internal class DefaultPaymentElementLoaderTest {
paymentMethods = PAYMENT_METHODS,
permissions = CustomerState.Permissions(
canRemovePaymentMethods = true,
+ canRemoveLastPaymentMethod = true,
canRemoveDuplicates = false,
),
),
@@ -1380,6 +1381,7 @@ internal class DefaultPaymentElementLoaderTest {
paymentMethods = cards,
permissions = CustomerState.Permissions(
canRemovePaymentMethods = false,
+ canRemoveLastPaymentMethod = false,
canRemoveDuplicates = true,
),
)
@@ -1396,6 +1398,7 @@ internal class DefaultPaymentElementLoaderTest {
session = createElementsSessionCustomerSession(
ElementsSession.Customer.Components.MobilePaymentElement.Enabled(
isPaymentMethodRemoveEnabled = true,
+ canRemoveLastPaymentMethod = true,
isPaymentMethodSaveEnabled = false,
allowRedisplayOverride = null,
)
@@ -1421,6 +1424,7 @@ internal class DefaultPaymentElementLoaderTest {
assertThat(state.customer?.permissions).isEqualTo(
CustomerState.Permissions(
canRemovePaymentMethods = true,
+ canRemoveLastPaymentMethod = true,
canRemoveDuplicates = true,
)
)
@@ -1437,6 +1441,7 @@ internal class DefaultPaymentElementLoaderTest {
ElementsSession.Customer.Components.MobilePaymentElement.Enabled(
isPaymentMethodRemoveEnabled = false,
isPaymentMethodSaveEnabled = false,
+ canRemoveLastPaymentMethod = true,
allowRedisplayOverride = null,
)
),
@@ -1461,6 +1466,7 @@ internal class DefaultPaymentElementLoaderTest {
assertThat(state.customer?.permissions).isEqualTo(
CustomerState.Permissions(
canRemovePaymentMethods = false,
+ canRemoveLastPaymentMethod = true,
canRemoveDuplicates = true,
)
)
@@ -1477,6 +1483,7 @@ internal class DefaultPaymentElementLoaderTest {
ElementsSession.Customer.Components.MobilePaymentElement.Enabled(
isPaymentMethodRemoveEnabled = false,
isPaymentMethodSaveEnabled = false,
+ canRemoveLastPaymentMethod = true,
allowRedisplayOverride = null,
)
),
@@ -1501,6 +1508,7 @@ internal class DefaultPaymentElementLoaderTest {
assertThat(state.customer?.permissions).isEqualTo(
CustomerState.Permissions(
canRemovePaymentMethods = false,
+ canRemoveLastPaymentMethod = true,
canRemoveDuplicates = true,
)
)
@@ -1528,6 +1536,7 @@ internal class DefaultPaymentElementLoaderTest {
assertThat(state.customer?.permissions).isEqualTo(
CustomerState.Permissions(
canRemovePaymentMethods = true,
+ canRemoveLastPaymentMethod = true,
canRemoveDuplicates = false,
)
)
@@ -1651,6 +1660,7 @@ internal class DefaultPaymentElementLoaderTest {
paymentMethods = cards,
permissions = CustomerState.Permissions(
canRemovePaymentMethods = true,
+ canRemoveLastPaymentMethod = true,
canRemoveDuplicates = false,
),
)
@@ -2107,6 +2117,73 @@ internal class DefaultPaymentElementLoaderTest {
)
}
+ @Test
+ fun `When using 'LegacyEphemeralKey',last PM permission should be true if config value is true`() =
+ removeLastPaymentMethodTest(
+ customer = PaymentSheet.CustomerConfiguration(
+ id = "cus_1",
+ ephemeralKeySecret = "ek_123",
+ ),
+ canRemoveLastPaymentMethodFromConfig = true,
+ ) { permissions ->
+ assertThat(permissions.canRemoveLastPaymentMethod).isTrue()
+ }
+
+ @OptIn(ExperimentalCustomerSessionApi::class)
+ @Test
+ fun `When using 'CustomerSession', last PM permission should be true if server & config value is true`() =
+ removeLastPaymentMethodTest(
+ customer = PaymentSheet.CustomerConfiguration.createWithCustomerSession(
+ id = "cus_1",
+ clientSecret = "cuss_123",
+ ),
+ canRemoveLastPaymentMethodFromServer = true,
+ canRemoveLastPaymentMethodFromConfig = true,
+ ) { permissions ->
+ assertThat(permissions.canRemoveLastPaymentMethod).isTrue()
+ }
+
+ private fun removeLastPaymentMethodTest(
+ customer: PaymentSheet.CustomerConfiguration,
+ shouldDisableMobilePaymentElement: Boolean = false,
+ canRemoveLastPaymentMethodFromServer: Boolean = true,
+ canRemoveLastPaymentMethodFromConfig: Boolean = true,
+ test: (CustomerState.Permissions) -> Unit,
+ ) = runTest {
+ val loader = createPaymentElementLoader(
+ customer = ElementsSession.Customer(
+ paymentMethods = PaymentMethodFactory.cards(4),
+ session = createElementsSessionCustomerSession(
+ if (shouldDisableMobilePaymentElement) {
+ ElementsSession.Customer.Components.MobilePaymentElement.Disabled
+ } else {
+ ElementsSession.Customer.Components.MobilePaymentElement.Enabled(
+ isPaymentMethodRemoveEnabled = false,
+ isPaymentMethodSaveEnabled = false,
+ canRemoveLastPaymentMethod = canRemoveLastPaymentMethodFromServer,
+ allowRedisplayOverride = null,
+ )
+ }
+ ),
+ defaultPaymentMethod = null,
+ )
+ )
+
+ val state = loader.load(
+ initializationMode = PaymentElementLoader.InitializationMode.PaymentIntent(
+ clientSecret = "client_secret"
+ ),
+ paymentSheetConfiguration = PaymentSheet.Configuration(
+ merchantDisplayName = "Merchant, Inc.",
+ customer = customer,
+ allowsRemovalOfLastSavedPaymentMethod = canRemoveLastPaymentMethodFromConfig
+ ),
+ initializedViaCompose = false,
+ ).getOrThrow()
+
+ test(requireNotNull(state.customer).permissions)
+ }
+
private suspend fun testExternalPaymentMethods(
requestedExternalPaymentMethods: List,
externalPaymentMethodData: String?,
@@ -2200,6 +2277,7 @@ internal class DefaultPaymentElementLoaderTest {
ElementsSession.Customer.Components.MobilePaymentElement.Enabled(
isPaymentMethodSaveEnabled = it,
isPaymentMethodRemoveEnabled = true,
+ canRemoveLastPaymentMethod = true,
allowRedisplayOverride = null,
)
} ?: ElementsSession.Customer.Components.MobilePaymentElement.Disabled
diff --git a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/verticalmode/VerticalModeInitialScreenFactoryTest.kt b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/verticalmode/VerticalModeInitialScreenFactoryTest.kt
index 9fbc72c5762..35d8a56bc81 100644
--- a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/verticalmode/VerticalModeInitialScreenFactoryTest.kt
+++ b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/verticalmode/VerticalModeInitialScreenFactoryTest.kt
@@ -74,6 +74,7 @@ class VerticalModeInitialScreenFactoryTest {
paymentMethods = listOf(PaymentMethodFixtures.CARD_PAYMENT_METHOD),
permissions = CustomerState.Permissions(
canRemovePaymentMethods = true,
+ canRemoveLastPaymentMethod = true,
canRemoveDuplicates = true,
)
)
diff --git a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/viewmodels/PaymentOptionsItemsMapperTest.kt b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/viewmodels/PaymentOptionsItemsMapperTest.kt
index 9826fe251d4..3883b836a17 100644
--- a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/viewmodels/PaymentOptionsItemsMapperTest.kt
+++ b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/viewmodels/PaymentOptionsItemsMapperTest.kt
@@ -91,6 +91,7 @@ class PaymentOptionsItemsMapperTest {
paymentMethods = paymentMethods,
permissions = CustomerState.Permissions(
canRemovePaymentMethods = true,
+ canRemoveLastPaymentMethod = true,
canRemoveDuplicates = false,
)
)