diff --git a/CHANGELOG.md b/CHANGELOG.md index 7070bd14fe3..bbc52ffd759 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # CHANGELOG ## XX.XX.XX - 2023-XX-XX +* [ADDED][7269](https://github.com/stripe/stripe-android/pull/7269) PaymentSheet now supports Ideal with SetupIntent and PaymentIntent with setup for future usage. ## 20.29.2 - 2023-09-05 * [ADDED][7263](https://github.com/stripe/stripe-android/pull/7263) PaymentSheet now supports Bancontact SetupIntent and PaymentIntent with setup for future usage. diff --git a/payments-ui-core/src/main/java/com/stripe/android/paymentsheet/forms/PaymentMethodRequirements.kt b/payments-ui-core/src/main/java/com/stripe/android/paymentsheet/forms/PaymentMethodRequirements.kt index 07fedf702fd..ff82de7ad0f 100644 --- a/payments-ui-core/src/main/java/com/stripe/android/paymentsheet/forms/PaymentMethodRequirements.kt +++ b/payments-ui-core/src/main/java/com/stripe/android/paymentsheet/forms/PaymentMethodRequirements.kt @@ -121,30 +121,13 @@ internal val SofortRequirement = PaymentMethodRequirements( internal val IdealRequirement = PaymentMethodRequirements( piRequirements = emptySet(), + siRequirements = setOf(Delayed), /** - * Currently we will not support this PaymentMethod for use with PI w/SFU, - * or SI until there is a way of retrieving valid mandates associated with a customer PM. - * - * The reason we are excluding it is because after PI w/SFU set or PI - * is used, the payment method appears as a SEPA payment method attached - * to a customer. Without this block the SEPA payment method would - * show in PaymentSheet. If the user used this save payment method - * we would have no way to know if the existing mandate was valid or how - * to request the user to re-accept the mandate. - * - * SEPA Debit does support PI w/SFU and SI (both with and without a customer), - * and it is Delayed in this configuration. - */ - siRequirements = null, - - /** - * This PM cannot be attached to a customer, it should be noted that it - * will be attached as a SEPA Debit payment method and have the requirements - * of that PaymentMethod, but for now SEPA is not supported either so we will - * call it false. + * PM will be attached as a SEPA Debit payment method and have the requirements + * of that PaymentMethod. */ - confirmPMFromCustomer = false + confirmPMFromCustomer = true, ) internal val SepaDebitRequirement = PaymentMethodRequirements( diff --git a/payments-ui-core/src/main/java/com/stripe/android/ui/core/forms/resources/LpmRepository.kt b/payments-ui-core/src/main/java/com/stripe/android/ui/core/forms/resources/LpmRepository.kt index d809319a0f9..752aa80477c 100644 --- a/payments-ui-core/src/main/java/com/stripe/android/ui/core/forms/resources/LpmRepository.kt +++ b/payments-ui-core/src/main/java/com/stripe/android/ui/core/forms/resources/LpmRepository.kt @@ -298,18 +298,31 @@ class LpmRepository constructor( requirement = SofortRequirement, formSpec = LayoutSpec(sharedDataSpec.fields) ) - PaymentMethod.Type.Ideal.code -> SupportedPaymentMethod( - code = "ideal", - requiresMandate = true, - mandateRequirement = MandateRequirement.Always, - displayNameResource = R.string.stripe_paymentsheet_payment_method_ideal, - iconResource = R.drawable.stripe_ic_paymentsheet_pm_ideal, - lightThemeIconUrl = sharedDataSpec.selectorIcon?.lightThemePng, - darkThemeIconUrl = sharedDataSpec.selectorIcon?.darkThemePng, - tintIconOnSelection = false, - requirement = IdealRequirement, - formSpec = LayoutSpec(sharedDataSpec.fields) - ) + PaymentMethod.Type.Ideal.code -> { + val isSfu = (stripeIntent as? PaymentIntent)?.setupFutureUsage != null + val isSetupIntent = stripeIntent is SetupIntent + val localLayoutSpecs: List = if (isSfu || isSetupIntent) { + listOf( + EmailSpec(), + SepaMandateTextSpec() + ) + } else { + emptyList() + } + + SupportedPaymentMethod( + code = "ideal", + requiresMandate = true, + mandateRequirement = MandateRequirement.Always, + displayNameResource = R.string.stripe_paymentsheet_payment_method_ideal, + iconResource = R.drawable.stripe_ic_paymentsheet_pm_ideal, + lightThemeIconUrl = sharedDataSpec.selectorIcon?.lightThemePng, + darkThemeIconUrl = sharedDataSpec.selectorIcon?.darkThemePng, + tintIconOnSelection = false, + requirement = IdealRequirement, + formSpec = LayoutSpec(sharedDataSpec.fields + localLayoutSpecs) + ) + } PaymentMethod.Type.SepaDebit.code -> SupportedPaymentMethod( code = "sepa_debit", requiresMandate = true, diff --git a/paymentsheet-example/src/androidTest/java/com/stripe/android/lpm/TestIdeal.kt b/paymentsheet-example/src/androidTest/java/com/stripe/android/lpm/TestIdeal.kt index 7365688f94b..17a0001331b 100644 --- a/paymentsheet-example/src/androidTest/java/com/stripe/android/lpm/TestIdeal.kt +++ b/paymentsheet-example/src/androidTest/java/com/stripe/android/lpm/TestIdeal.kt @@ -2,6 +2,10 @@ package com.stripe.android.lpm import androidx.test.ext.junit.runners.AndroidJUnit4 import com.stripe.android.BaseLpmTest +import com.stripe.android.test.core.AuthorizeAction +import com.stripe.android.test.core.Automatic +import com.stripe.android.test.core.DelayedPMs +import com.stripe.android.test.core.IntentType import org.junit.Test import org.junit.runner.RunWith @@ -18,6 +22,29 @@ internal class TestIdeal : BaseLpmTest() { ) } + @Test + fun testIdealSfu() { + testDriver.confirmNewOrGuestComplete( + testParameters = ideal.copy( + delayed = DelayedPMs.On, + automatic = Automatic.On, + intentType = IntentType.PayWithSetup, + ), + ) + } + + @Test + fun testIdealSetup() { + testDriver.confirmNewOrGuestComplete( + testParameters = ideal.copy( + delayed = DelayedPMs.On, + automatic = Automatic.On, + intentType = IntentType.Setup, + authorizationAction = AuthorizeAction.AuthorizeSetup, + ), + ) + } + @Test fun testIdealInCustomFlow() { testDriver.confirmCustom( diff --git a/paymentsheet/src/test/resources/ideal-support.csv b/paymentsheet/src/test/resources/ideal-support.csv index de39e507d0d..68fb614b03e 100644 --- a/paymentsheet/src/test/resources/ideal-support.csv +++ b/paymentsheet/src/test/resources/ideal-support.csv @@ -1,49 +1,49 @@ lpm, hasCustomer, allowsDelayedPayment, intentSetupFutureUsage, intentHasShipping, intentLpms, supportCustomerSavedCard, formExists, formType, supportsAdding -ideal, true, true, off_session, false, card/ideal, false, false, not available, false -ideal, true, true, off_session, false, card/eps/ideal, false, false, not available, false +ideal, true, true, off_session, false, card/ideal, true, true, merchantRequiredSave, true +ideal, true, true, off_session, false, card/eps/ideal, true, true, merchantRequiredSave, true ideal, true, false, off_session, false, card/ideal, false, false, not available, false ideal, true, false, off_session, false, card/eps/ideal, false, false, not available, false -ideal, true, true, on_session, false, card/ideal, false, false, not available, false -ideal, true, true, on_session, false, card/eps/ideal, false, false, not available, false +ideal, true, true, on_session, false, card/ideal, true, true, merchantRequiredSave, true +ideal, true, true, on_session, false, card/eps/ideal, true, true, merchantRequiredSave, true ideal, true, false, on_session, false, card/ideal, false, false, not available, false ideal, true, false, on_session, false, card/eps/ideal, false, false, not available, false -ideal, true, true, null, false, card/ideal, false, true, oneTime, true -ideal, true, true, null, false, card/eps/ideal, false, true, oneTime, true -ideal, true, false, null, false, card/ideal, false, true, oneTime, true -ideal, true, false, null, false, card/eps/ideal, false, true, oneTime, true -ideal, false, true, off_session, false, card/ideal, false, false, not available, false -ideal, false, true, off_session, false, card/eps/ideal, false, false, not available, false +ideal, true, true, null, false, card/ideal, true, true, userSelectedSave, true +ideal, true, true, null, false, card/eps/ideal, true, true, userSelectedSave, true +ideal, true, false, null, false, card/ideal, true, true, oneTime, true +ideal, true, false, null, false, card/eps/ideal, true, true, oneTime, true +ideal, false, true, off_session, false, card/ideal, true, true, merchantRequiredSave, true +ideal, false, true, off_session, false, card/eps/ideal, true, true, merchantRequiredSave, true ideal, false, false, off_session, false, card/ideal, false, false, not available, false ideal, false, false, off_session, false, card/eps/ideal, false, false, not available, false -ideal, false, true, on_session, false, card/ideal, false, false, not available, false -ideal, false, true, on_session, false, card/eps/ideal, false, false, not available, false +ideal, false, true, on_session, false, card/ideal, true, true, merchantRequiredSave, true +ideal, false, true, on_session, false, card/eps/ideal, true, true, merchantRequiredSave, true ideal, false, false, on_session, false, card/ideal, false, false, not available, false ideal, false, false, on_session, false, card/eps/ideal, false, false, not available, false -ideal, false, true, null, false, card/ideal, false, true, oneTime, true -ideal, false, true, null, false, card/eps/ideal, false, true, oneTime, true -ideal, false, false, null, false, card/ideal, false, true, oneTime, true -ideal, false, false, null, false, card/eps/ideal, false, true, oneTime, true -ideal, true, true, off_session, true, card/ideal, false, false, not available, false -ideal, true, true, off_session, true, card/eps/ideal, false, false, not available, false +ideal, false, true, null, false, card/ideal, true, true, oneTime, true +ideal, false, true, null, false, card/eps/ideal, true, true, oneTime, true +ideal, false, false, null, false, card/ideal, true, true, oneTime, true +ideal, false, false, null, false, card/eps/ideal, true, true, oneTime, true +ideal, true, true, off_session, true, card/ideal, true, true, merchantRequiredSave, true +ideal, true, true, off_session, true, card/eps/ideal, true, true, merchantRequiredSave, true ideal, true, false, off_session, true, card/ideal, false, false, not available, false ideal, true, false, off_session, true, card/eps/ideal, false, false, not available, false -ideal, true, true, on_session, true, card/ideal, false, false, not available, false -ideal, true, true, on_session, true, card/eps/ideal, false, false, not available, false +ideal, true, true, on_session, true, card/ideal, true, true, merchantRequiredSave, true +ideal, true, true, on_session, true, card/eps/ideal, true, true, merchantRequiredSave, true ideal, true, false, on_session, true, card/ideal, false, false, not available, false ideal, true, false, on_session, true, card/eps/ideal, false, false, not available, false -ideal, true, true, null, true, card/ideal, false, true, oneTime, true -ideal, true, true, null, true, card/eps/ideal, false, true, oneTime, true -ideal, true, false, null, true, card/ideal, false, true, oneTime, true -ideal, true, false, null, true, card/eps/ideal, false, true, oneTime, true -ideal, false, true, off_session, true, card/ideal, false, false, not available, false -ideal, false, true, off_session, true, card/eps/ideal, false, false, not available, false +ideal, true, true, null, true, card/ideal, true, true, userSelectedSave, true +ideal, true, true, null, true, card/eps/ideal, true, true, userSelectedSave, true +ideal, true, false, null, true, card/ideal, true, true, oneTime, true +ideal, true, false, null, true, card/eps/ideal, true, true, oneTime, true +ideal, false, true, off_session, true, card/ideal, true, true, merchantRequiredSave, true +ideal, false, true, off_session, true, card/eps/ideal, true, true, merchantRequiredSave, true ideal, false, false, off_session, true, card/ideal, false, false, not available, false ideal, false, false, off_session, true, card/eps/ideal, false, false, not available, false -ideal, false, true, on_session, true, card/ideal, false, false, not available, false -ideal, false, true, on_session, true, card/eps/ideal, false, false, not available, false +ideal, false, true, on_session, true, card/ideal, true, true, merchantRequiredSave, true +ideal, false, true, on_session, true, card/eps/ideal, true, true, merchantRequiredSave, true ideal, false, false, on_session, true, card/ideal, false, false, not available, false ideal, false, false, on_session, true, card/eps/ideal, false, false, not available, false -ideal, false, true, null, true, card/ideal, false, true, oneTime, true -ideal, false, true, null, true, card/eps/ideal, false, true, oneTime, true -ideal, false, false, null, true, card/ideal, false, true, oneTime, true -ideal, false, false, null, true, card/eps/ideal, false, true, oneTime, true +ideal, false, true, null, true, card/ideal, true, true, oneTime, true +ideal, false, true, null, true, card/eps/ideal, true, true, oneTime, true +ideal, false, false, null, true, card/ideal, true, true, oneTime, true +ideal, false, false, null, true, card/eps/ideal, true, true, oneTime, true