diff --git a/payments-core/src/main/java/com/stripe/android/model/PaymentMethodCreateParams.kt b/payments-core/src/main/java/com/stripe/android/model/PaymentMethodCreateParams.kt index 0d3f25e9da7..06ddea7ff5a 100644 --- a/payments-core/src/main/java/com/stripe/android/model/PaymentMethodCreateParams.kt +++ b/payments-core/src/main/java/com/stripe/android/model/PaymentMethodCreateParams.kt @@ -505,9 +505,31 @@ data class PaymentMethodCreateParams internal constructor( ) } - private companion object { + internal companion object { private const val PARAM_ACCOUNT_NUMBER: String = "account_number" private const val PARAM_SORT_CODE: String = "sort_code" + + internal fun fromParams(params: PaymentMethodCreateParams): BacsDebit? { + val code = PaymentMethod.Type.BacsDebit.code + + val bacsParams = params.toParamMap()[code] as? Map<*, *> + + val accountNumber = bacsParams?.get( + PARAM_ACCOUNT_NUMBER + ) as? String + + val sortCode = bacsParams?.get( + PARAM_SORT_CODE + ) as? String + + return when { + accountNumber != null && sortCode != null -> BacsDebit( + accountNumber = accountNumber, + sortCode = sortCode + ) + else -> null + } + } } } @@ -1091,5 +1113,42 @@ data class PaymentMethodCreateParams internal constructor( productUsage = productUsage ) } + + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) + fun createBacsFromParams( + params: PaymentMethodCreateParams + ): BacsDebit? { + return BacsDebit.fromParams(params) + } + + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) + fun getNameFromParams( + params: PaymentMethodCreateParams + ): String? { + return params.billingDetails?.name ?: getBillingDetailsValueFromOverrideParams( + params, + PaymentMethod.BillingDetails.PARAM_NAME + ) + } + + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) + fun getEmailFromParams( + params: PaymentMethodCreateParams + ): String? { + return params.billingDetails?.email ?: getBillingDetailsValueFromOverrideParams( + params, + PaymentMethod.BillingDetails.PARAM_EMAIL + ) + } + + private fun getBillingDetailsValueFromOverrideParams( + params: PaymentMethodCreateParams, + key: String + ): String? { + val billingDetailsParams = params.overrideParamMap + ?.get(PARAM_BILLING_DETAILS) as? Map<*, *> + + return billingDetailsParams?.get(key) as? String + } } } diff --git a/payments-core/src/test/java/com/stripe/android/model/PaymentMethodCreateParamsTest.kt b/payments-core/src/test/java/com/stripe/android/model/PaymentMethodCreateParamsTest.kt index a423bdf6530..e2174f67621 100644 --- a/payments-core/src/test/java/com/stripe/android/model/PaymentMethodCreateParamsTest.kt +++ b/payments-core/src/test/java/com/stripe/android/model/PaymentMethodCreateParamsTest.kt @@ -263,6 +263,105 @@ class PaymentMethodCreateParamsTest { ) } + @Test + fun `createBacsFromParams should return a 'BacsDebit' instance correctly`() { + val params = PaymentMethodCreateParams.create( + bacsDebit = PaymentMethodCreateParams.BacsDebit( + accountNumber = "00012345", + sortCode = "108800" + ), + billingDetails = PaymentMethod.BillingDetails() + ) + + assertThat( + PaymentMethodCreateParams.createBacsFromParams(params) + ).isEqualTo( + PaymentMethodCreateParams.BacsDebit( + accountNumber = "00012345", + sortCode = "108800" + ) + ) + + val overrideParams = PaymentMethodCreateParams.createWithOverride( + code = "bacs_debit", + overrideParamMap = mapOf( + "bacs_debit" to mapOf( + "account_number" to "00012345", + "sort_code" to "108800" + ) + ), + productUsage = setOf(), + requiresMandate = false + ) + + assertThat( + PaymentMethodCreateParams.createBacsFromParams(overrideParams) + ).isEqualTo( + PaymentMethodCreateParams.BacsDebit( + accountNumber = "00012345", + sortCode = "108800" + ) + ) + } + + @Test + fun `getNameFromOverrideParams should return name from billing details if available`() { + val params = PaymentMethodCreateParams.create( + card = PaymentMethodCreateParams.Card(), + billingDetails = PaymentMethod.BillingDetails( + name = "John Doe" + ) + ) + + assertThat( + PaymentMethodCreateParams.getNameFromParams(params) + ).isEqualTo("John Doe") + + val overrideParams = PaymentMethodCreateParams.createWithOverride( + code = "bacs_debit", + overrideParamMap = mapOf( + "billing_details" to mapOf( + "name" to "John Doe" + ) + ), + productUsage = setOf(), + requiresMandate = false + ) + + assertThat( + PaymentMethodCreateParams.getNameFromParams(overrideParams) + ).isEqualTo("John Doe") + } + + @Test + fun `getEmailFromOverrideParams should return email from billing details if available`() { + val params = PaymentMethodCreateParams.create( + card = PaymentMethodCreateParams.Card(), + billingDetails = PaymentMethod.BillingDetails( + email = "johndoe@email.com" + ) + ) + + assertThat( + PaymentMethodCreateParams.getEmailFromParams(params) + ).isEqualTo("johndoe@email.com") + + val overrideParams = PaymentMethodCreateParams.createWithOverride( + code = "card", + overrideParamMap = mapOf( + "billing_details" to mapOf( + "email" to "johndoe@email.com" + ) + ), + productUsage = setOf(), + requiresMandate = false + ) + + assertThat( + PaymentMethodCreateParams.getEmailFromParams(overrideParams) + ).isEqualTo("johndoe@email.com") + } + private fun createFpx(): PaymentMethodCreateParams { return PaymentMethodCreateParams.create( PaymentMethodCreateParams.Fpx(bank = "hsbc"), diff --git a/paymentsheet/api/paymentsheet.api b/paymentsheet/api/paymentsheet.api index 558fd76713d..2657ccace00 100644 --- a/paymentsheet/api/paymentsheet.api +++ b/paymentsheet/api/paymentsheet.api @@ -1612,6 +1612,14 @@ public final class com/stripe/android/paymentsheet/paymentdatacollection/bacs/Ba public synthetic fun newArray (I)[Ljava/lang/Object; } +public final class com/stripe/android/paymentsheet/paymentdatacollection/bacs/BacsMandateConfirmationResult$ModifyDetails$Creator : android/os/Parcelable$Creator { + public fun ()V + public final fun createFromParcel (Landroid/os/Parcel;)Lcom/stripe/android/paymentsheet/paymentdatacollection/bacs/BacsMandateConfirmationResult$ModifyDetails; + public synthetic fun createFromParcel (Landroid/os/Parcel;)Ljava/lang/Object; + public final fun newArray (I)[Lcom/stripe/android/paymentsheet/paymentdatacollection/bacs/BacsMandateConfirmationResult$ModifyDetails; + public synthetic fun newArray (I)[Ljava/lang/Object; +} + public final class com/stripe/android/paymentsheet/paymentdatacollection/bacs/ComposableSingletons$BacsMandateConfirmationFormKt { public static final field INSTANCE Lcom/stripe/android/paymentsheet/paymentdatacollection/bacs/ComposableSingletons$BacsMandateConfirmationFormKt; public static field lambda-1 Lkotlin/jvm/functions/Function2; diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/PaymentSheetActivity.kt b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/PaymentSheetActivity.kt index cda325c12bc..df8728fefb1 100644 --- a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/PaymentSheetActivity.kt +++ b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/PaymentSheetActivity.kt @@ -15,6 +15,7 @@ import androidx.lifecycle.lifecycleScope import com.stripe.android.common.ui.BottomSheet import com.stripe.android.common.ui.rememberBottomSheetState import com.stripe.android.googlepaylauncher.GooglePayPaymentMethodLauncherContractV2 +import com.stripe.android.paymentsheet.paymentdatacollection.bacs.BacsMandateConfirmationContract import com.stripe.android.paymentsheet.ui.BaseSheetActivity import com.stripe.android.paymentsheet.ui.PaymentSheetScreen import com.stripe.android.uicore.StripeTheme @@ -58,6 +59,13 @@ internal class PaymentSheetActivity : BaseSheetActivity() { ) ) + viewModel.setupBacsMandateConfirmation( + registerForActivityResult( + BacsMandateConfirmationContract(), + viewModel::onBacsMandateResult + ) + ) + setContent { StripeTheme { val isProcessing by viewModel.processing.collectAsState() diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/PaymentSheetViewModel.kt b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/PaymentSheetViewModel.kt index 22b3603214b..e803434c4ab 100644 --- a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/PaymentSheetViewModel.kt +++ b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/PaymentSheetViewModel.kt @@ -27,6 +27,7 @@ import com.stripe.android.model.ConfirmPaymentIntentParams import com.stripe.android.model.ConfirmSetupIntentParams import com.stripe.android.model.ConfirmStripeIntentParams import com.stripe.android.model.PaymentIntent +import com.stripe.android.model.PaymentMethod import com.stripe.android.model.SetupIntent import com.stripe.android.model.StripeIntent import com.stripe.android.payments.paymentlauncher.InternalPaymentResult @@ -44,6 +45,11 @@ import com.stripe.android.paymentsheet.model.PaymentSelection import com.stripe.android.paymentsheet.model.PaymentSheetViewState import com.stripe.android.paymentsheet.model.currency import com.stripe.android.paymentsheet.navigation.PaymentSheetScreen +import com.stripe.android.paymentsheet.paymentdatacollection.bacs.BacsMandateConfirmationContract +import com.stripe.android.paymentsheet.paymentdatacollection.bacs.BacsMandateConfirmationLauncher +import com.stripe.android.paymentsheet.paymentdatacollection.bacs.BacsMandateConfirmationLauncherFactory +import com.stripe.android.paymentsheet.paymentdatacollection.bacs.BacsMandateConfirmationResult +import com.stripe.android.paymentsheet.paymentdatacollection.bacs.BacsMandateData import com.stripe.android.paymentsheet.repositories.CustomerRepository import com.stripe.android.paymentsheet.state.GooglePayState import com.stripe.android.paymentsheet.state.PaymentSheetLoader @@ -89,6 +95,7 @@ internal class PaymentSheetViewModel @Inject internal constructor( lpmRepository: LpmRepository, private val paymentLauncherFactory: StripePaymentLauncherAssistedFactory, private val googlePayPaymentMethodLauncherFactory: GooglePayPaymentMethodLauncherFactory, + private val bacsMandateConfirmationLauncherFactory: BacsMandateConfirmationLauncherFactory, logger: Logger, @IOContext workContext: CoroutineContext, savedStateHandle: SavedStateHandle, @@ -159,6 +166,8 @@ internal class PaymentSheetViewModel @Inject internal constructor( private var googlePayPaymentMethodLauncher: GooglePayPaymentMethodLauncher? = null + private var bacsMandateConfirmationLauncher: BacsMandateConfirmationLauncher? = null + private var deferredIntentConfirmationType: DeferredIntentConfirmationType? = null @VisibleForTesting @@ -347,6 +356,12 @@ internal class PaymentSheetViewModel @Inject internal constructor( } } + fun setupBacsMandateConfirmation( + activityLauncher: ActivityResultLauncher + ) { + bacsMandateConfirmationLauncher = bacsMandateConfirmationLauncherFactory.create(activityLauncher) + } + private fun resetViewState(userErrorMessage: String? = null) { viewState.value = PaymentSheetViewState.Reset(userErrorMessage?.let { UserErrorMessage(it) }) @@ -393,6 +408,32 @@ internal class PaymentSheetViewModel @Inject internal constructor( label = args.googlePayConfig?.label, ) } + } else if ( + paymentSelection is PaymentSelection.New.GenericPaymentMethod && + paymentSelection.paymentMethodCreateParams.typeCode == PaymentMethod.Type.BacsDebit.code + ) { + BacsMandateData.fromPaymentSelection(paymentSelection)?.let { data -> + runCatching { + requireNotNull(bacsMandateConfirmationLauncher) + }.onSuccess { launcher -> + launcher.launch( + data = data, + appearance = config.appearance + ) + }.onFailure { + resetViewState( + userErrorMessage = getApplication() + .resources + .getString(R.string.stripe_something_went_wrong) + ) + } + } ?: run { + resetViewState( + userErrorMessage = getApplication() + .resources + .getString(R.string.stripe_something_went_wrong) + ) + } } else { confirmPaymentSelection(paymentSelection) } @@ -642,6 +683,23 @@ internal class PaymentSheetViewModel @Inject internal constructor( } } + internal fun onBacsMandateResult(result: BacsMandateConfirmationResult) { + when (result) { + is BacsMandateConfirmationResult.Confirmed -> { + val paymentSelection = selection.value + + if ( + paymentSelection is PaymentSelection.New.GenericPaymentMethod && + paymentSelection.paymentMethodCreateParams.typeCode == PaymentMethod.Type.BacsDebit.code + ) { + confirmPaymentSelection(paymentSelection) + } + } + is BacsMandateConfirmationResult.ModifyDetails, + is BacsMandateConfirmationResult.Cancelled -> resetViewState(userErrorMessage = null) + } + } + override fun onFatal(throwable: Throwable) { logger.error("Payment Sheet error", throwable) mostRecentError = throwable diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/flowcontroller/DefaultFlowController.kt b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/flowcontroller/DefaultFlowController.kt index e3e987e24c1..81a46859193 100644 --- a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/flowcontroller/DefaultFlowController.kt +++ b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/flowcontroller/DefaultFlowController.kt @@ -51,6 +51,11 @@ import com.stripe.android.paymentsheet.model.PaymentOption import com.stripe.android.paymentsheet.model.PaymentOptionFactory import com.stripe.android.paymentsheet.model.PaymentSelection import com.stripe.android.paymentsheet.model.currency +import com.stripe.android.paymentsheet.paymentdatacollection.bacs.BacsMandateConfirmationContract +import com.stripe.android.paymentsheet.paymentdatacollection.bacs.BacsMandateConfirmationLauncher +import com.stripe.android.paymentsheet.paymentdatacollection.bacs.BacsMandateConfirmationLauncherFactory +import com.stripe.android.paymentsheet.paymentdatacollection.bacs.BacsMandateConfirmationResult +import com.stripe.android.paymentsheet.paymentdatacollection.bacs.BacsMandateData import com.stripe.android.paymentsheet.state.PaymentSheetState import com.stripe.android.paymentsheet.ui.SepaMandateContract import com.stripe.android.paymentsheet.ui.SepaMandateResult @@ -88,6 +93,7 @@ internal class DefaultFlowController @Inject internal constructor( @Named(ENABLE_LOGGING) private val enableLogging: Boolean, @Named(PRODUCT_USAGE) private val productUsage: Set, private val googlePayPaymentMethodLauncherFactory: GooglePayPaymentMethodLauncherFactory, + bacsMandateConfirmationLauncherFactory: BacsMandateConfirmationLauncherFactory, private val linkLauncher: LinkPaymentLauncher, private val configurationHandler: FlowControllerConfigurationHandler, private val intentConfirmationInterceptor: IntentConfirmationInterceptor, @@ -96,6 +102,7 @@ internal class DefaultFlowController @Inject internal constructor( private val googlePayActivityLauncher: ActivityResultLauncher private val sepaMandateActivityLauncher: ActivityResultLauncher + private val bacsMandateConfirmationLauncher: BacsMandateConfirmationLauncher /** * [FlowControllerComponent] is hold to inject into [Activity]s and created @@ -145,11 +152,21 @@ internal class DefaultFlowController @Inject internal constructor( ::onSepaMandateResult, ) + val bacsMandateConfirmationActivityLauncher = activityResultRegistryOwner.register( + BacsMandateConfirmationContract(), + ::onBacsMandateResult + ) + + bacsMandateConfirmationLauncher = bacsMandateConfirmationLauncherFactory.create( + bacsMandateConfirmationActivityLauncher + ) + val activityResultLaunchers = setOf( paymentLauncherActivityResultLauncher, paymentOptionActivityLauncher, googlePayActivityLauncher, sepaMandateActivityLauncher, + bacsMandateConfirmationActivityLauncher ) linkLauncher.register( @@ -283,6 +300,26 @@ internal class DefaultFlowController @Inject internal constructor( is PaymentSelection.GooglePay -> launchGooglePay(state) is PaymentSelection.Link, is PaymentSelection.New.LinkInline -> confirmLink(paymentSelection, state) + is PaymentSelection.New.GenericPaymentMethod -> { + if (paymentSelection.paymentMethodCreateParams.typeCode == PaymentMethod.Type.BacsDebit.code) { + BacsMandateData.fromPaymentSelection(paymentSelection)?.let { data -> + bacsMandateConfirmationLauncher.launch( + data = data, + appearance = viewModel.previousConfigureRequest?.configuration?.appearance + ) + } ?: run { + paymentResultCallback.onPaymentSheetResult( + PaymentSheetResult.Failed( + BacsMandateException( + type = BacsMandateException.Type.MissingInformation + ) + ) + ) + } + } else { + confirmPaymentSelection(paymentSelection, state) + } + } is PaymentSelection.New, null -> confirmPaymentSelection(paymentSelection, state) is PaymentSelection.Saved -> { @@ -437,6 +474,44 @@ internal class DefaultFlowController @Inject internal constructor( } } + internal fun onBacsMandateResult( + result: BacsMandateConfirmationResult + ) { + when (result) { + is BacsMandateConfirmationResult.Confirmed -> { + runCatching { + requireNotNull(viewModel.state) + }.fold( + onSuccess = { state -> + val currentSelection = viewModel.paymentSelection + + if ( + currentSelection is PaymentSelection.New.GenericPaymentMethod && + currentSelection.paymentMethodCreateParams.typeCode == PaymentMethod.Type.BacsDebit.code + ) { + confirmPaymentSelection(currentSelection, state) + } else { + paymentResultCallback.onPaymentSheetResult( + PaymentSheetResult.Failed( + BacsMandateException( + type = BacsMandateException.Type.IncorrectSelection + ) + ) + ) + } + }, + onFailure = { error -> + paymentResultCallback.onPaymentSheetResult( + PaymentSheetResult.Failed(error) + ) + } + ) + } + is BacsMandateConfirmationResult.ModifyDetails -> presentPaymentOptions() + is BacsMandateConfirmationResult.Cancelled -> Unit + } + } + fun onLinkActivityResult(result: LinkActivityResult): Unit = when (result) { is LinkActivityResult.Canceled -> onPaymentResult(PaymentResult.Canceled) is LinkActivityResult.Failed -> onPaymentResult(PaymentResult.Failed(result.error)) @@ -638,6 +713,21 @@ internal class DefaultFlowController @Inject internal constructor( is PaymentResult.Failed -> PaymentSheetResult.Failed(throwable) } + class BacsMandateException( + val type: Type + ) : Exception() { + override val message: String = when (type) { + Type.MissingInformation -> + "Bacs requires the account's name, email, sort code, and account number be provided!" + Type.IncorrectSelection -> "Cannot confirm non-Bacs payment method with Bacs mandate" + } + + enum class Type { + MissingInformation, + IncorrectSelection + } + } + class GooglePayException( val throwable: Throwable ) : Exception(throwable) diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/injection/PaymentSheetCommonModule.kt b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/injection/PaymentSheetCommonModule.kt index 07d0fa8d876..6111e8a7a8f 100644 --- a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/injection/PaymentSheetCommonModule.kt +++ b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/injection/PaymentSheetCommonModule.kt @@ -20,6 +20,8 @@ import com.stripe.android.paymentsheet.analytics.DefaultEventReporter import com.stripe.android.paymentsheet.analytics.EventReporter import com.stripe.android.paymentsheet.flowcontroller.DefaultPaymentSelectionUpdater import com.stripe.android.paymentsheet.flowcontroller.PaymentSelectionUpdater +import com.stripe.android.paymentsheet.paymentdatacollection.bacs.BacsMandateConfirmationLauncherFactory +import com.stripe.android.paymentsheet.paymentdatacollection.bacs.DefaultBacsMandateConfirmationLauncherFactory import com.stripe.android.paymentsheet.repositories.CustomerApiRepository import com.stripe.android.paymentsheet.repositories.CustomerRepository import com.stripe.android.paymentsheet.repositories.ElementsSessionRepository @@ -120,6 +122,12 @@ internal abstract class PaymentSheetCommonModule { ) } + @Provides + @Singleton + fun provideBacsMandateConfirmationLauncherFactory(): BacsMandateConfirmationLauncherFactory { + return DefaultBacsMandateConfirmationLauncherFactory + } + @Provides @Singleton fun provideDurationProvider(): DurationProvider { diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/paymentdatacollection/bacs/BacsMandateButton.kt b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/paymentdatacollection/bacs/BacsMandateButton.kt index 0dcb476a5bb..20d2833cd2c 100644 --- a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/paymentdatacollection/bacs/BacsMandateButton.kt +++ b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/paymentdatacollection/bacs/BacsMandateButton.kt @@ -1,9 +1,6 @@ package com.stripe.android.paymentsheet.paymentdatacollection.bacs -import androidx.compose.foundation.BorderStroke -import androidx.compose.foundation.border import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.material.ButtonDefaults import androidx.compose.material.MaterialTheme import androidx.compose.material.Text @@ -11,9 +8,7 @@ import androidx.compose.material.TextButton import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.Shape -import androidx.compose.ui.unit.dp -import com.stripe.android.uicore.stripeColors +import com.stripe.android.common.ui.PrimaryButton @Composable internal fun BacsMandateButton(type: BacsMandateButtonType, label: String, onClick: () -> Unit) { @@ -23,41 +18,24 @@ internal fun BacsMandateButton(type: BacsMandateButtonType, label: String, onCli contentColor = MaterialTheme.colors.onPrimary ) BacsMandateButtonType.Secondary -> ButtonDefaults.buttonColors( - backgroundColor = MaterialTheme.stripeColors.component, - contentColor = MaterialTheme.stripeColors.onComponent + backgroundColor = Color.Transparent, + contentColor = MaterialTheme.colors.primary ) } - return TextButton( - modifier = Modifier - .fillMaxWidth() - .height(44.dp) - .addButtonBorder( - type, - MaterialTheme.shapes.small, - MaterialTheme.stripeColors.componentBorder - ), - shape = MaterialTheme.shapes.small, - colors = colors, - onClick = onClick - ) { - Text(text = label) - } -} - -private fun Modifier.addButtonBorder( - type: BacsMandateButtonType, - shape: Shape, - color: Color -): Modifier { - return when (type) { - BacsMandateButtonType.Primary -> this - BacsMandateButtonType.Secondary -> border( - border = BorderStroke( - width = 1.5.dp, - color = color, - ), - shape = shape + when (type) { + BacsMandateButtonType.Primary -> PrimaryButton( + label = label, + isEnabled = true, + onButtonClick = onClick ) + BacsMandateButtonType.Secondary -> TextButton( + modifier = Modifier.fillMaxWidth(), + shape = MaterialTheme.shapes.small, + colors = colors, + onClick = onClick + ) { + Text(text = label) + } } } diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/paymentdatacollection/bacs/BacsMandateConfirmationActivity.kt b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/paymentdatacollection/bacs/BacsMandateConfirmationActivity.kt index 63c8e64b1be..7f4c4014c00 100644 --- a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/paymentdatacollection/bacs/BacsMandateConfirmationActivity.kt +++ b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/paymentdatacollection/bacs/BacsMandateConfirmationActivity.kt @@ -14,10 +14,16 @@ import androidx.compose.runtime.LaunchedEffect import androidx.core.view.WindowCompat import com.stripe.android.common.ui.BottomSheet import com.stripe.android.common.ui.rememberBottomSheetState +import com.stripe.android.paymentsheet.R import com.stripe.android.paymentsheet.parseAppearance +import com.stripe.android.paymentsheet.ui.PaymentSheetScaffold +import com.stripe.android.paymentsheet.ui.PaymentSheetTopBar +import com.stripe.android.paymentsheet.ui.PaymentSheetTopBarState import com.stripe.android.uicore.StripeTheme import com.stripe.android.utils.AnimationConstants import kotlinx.coroutines.flow.collectLatest +import com.stripe.android.R as StripeR +import com.stripe.android.ui.core.R as StripeUiCoreR internal class BacsMandateConfirmationActivity : AppCompatActivity() { private val starterArgs: BacsMandateConfirmationContract.Args by lazy { @@ -44,10 +50,10 @@ internal class BacsMandateConfirmationActivity : AppCompatActivity() { renderEdgeToEdge() onBackPressedDispatcher.addCallback { - viewModel.handleViewAction(BacsMandateConfirmationViewAction.OnCancelPressed) + viewModel.handleViewAction(BacsMandateConfirmationViewAction.OnBackPressed) } - starterArgs.appearance.parseAppearance() + starterArgs.appearance?.parseAppearance() setContent { StripeTheme { @@ -67,10 +73,30 @@ internal class BacsMandateConfirmationActivity : AppCompatActivity() { BottomSheet( state = bottomSheetState, onDismissed = { - viewModel.handleViewAction(BacsMandateConfirmationViewAction.OnCancelPressed) + viewModel.handleViewAction(BacsMandateConfirmationViewAction.OnBackPressed) } ) { - BacsMandateConfirmationFormScreen(viewModel) + PaymentSheetScaffold( + topBar = { + PaymentSheetTopBar( + state = PaymentSheetTopBarState( + icon = R.drawable.stripe_ic_paymentsheet_close, + contentDescription = StripeUiCoreR.string.stripe_back, + isEnabled = true, + showEditMenu = false, + showTestModeLabel = false, + editMenuLabel = StripeR.string.stripe_edit + ), + handleBackPressed = { + viewModel.handleViewAction(BacsMandateConfirmationViewAction.OnBackPressed) + }, + toggleEditing = {}, + ) + }, + content = { + BacsMandateConfirmationFormScreen(viewModel) + } + ) } } } diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/paymentdatacollection/bacs/BacsMandateConfirmationContract.kt b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/paymentdatacollection/bacs/BacsMandateConfirmationContract.kt index 6dca732b510..75b43b0e328 100644 --- a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/paymentdatacollection/bacs/BacsMandateConfirmationContract.kt +++ b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/paymentdatacollection/bacs/BacsMandateConfirmationContract.kt @@ -29,7 +29,7 @@ internal class BacsMandateConfirmationContract : val nameOnAccount: String, val sortCode: String, val accountNumber: String, - val appearance: PaymentSheet.Appearance + val appearance: PaymentSheet.Appearance? ) : Parcelable { companion object { fun fromIntent(intent: Intent): Args? { diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/paymentdatacollection/bacs/BacsMandateConfirmationForm.kt b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/paymentdatacollection/bacs/BacsMandateConfirmationForm.kt index 326b8ceae21..aa2081fcb59 100644 --- a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/paymentdatacollection/bacs/BacsMandateConfirmationForm.kt +++ b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/paymentdatacollection/bacs/BacsMandateConfirmationForm.kt @@ -1,5 +1,6 @@ package com.stripe.android.paymentsheet.paymentdatacollection.bacs +import android.content.res.Configuration.UI_MODE_NIGHT_YES import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.background import androidx.compose.foundation.border @@ -9,8 +10,6 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll import androidx.compose.material.Icon import androidx.compose.material.MaterialTheme import androidx.compose.material.Text @@ -20,6 +19,8 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.SpanStyle @@ -31,9 +32,12 @@ import com.google.android.gms.common.util.VisibleForTesting import com.stripe.android.core.strings.resolvableString import com.stripe.android.paymentsheet.R import com.stripe.android.paymentsheet.utils.PaymentSheetContentPadding +import com.stripe.android.ui.core.elements.H4Text import com.stripe.android.uicore.StripeTheme +import com.stripe.android.uicore.shouldUseDarkDynamicColor import com.stripe.android.uicore.strings.resolve import com.stripe.android.uicore.stripeColors +import com.stripe.android.uicore.stripeTypography import com.stripe.android.uicore.text.Html import com.stripe.android.R as PaymentsCoreR import com.stripe.android.ui.core.R as PaymentsUiCoreR @@ -55,17 +59,17 @@ internal fun BacsMandateConfirmationFormView( viewActionHandler: (action: BacsMandateConfirmationViewAction) -> Unit, modifier: Modifier = Modifier ) { + val padding = dimensionResource(id = R.dimen.stripe_paymentsheet_outer_spacing_horizontal) + return Column( modifier = modifier - .verticalScroll(rememberScrollState()) .background(MaterialTheme.colors.surface) - .padding(16.dp), + .padding(horizontal = padding), verticalArrangement = Arrangement.spacedBy(16.dp) ) { - Text( + H4Text( text = stringResource(id = R.string.stripe_paymentsheet_bacs_mandate_title), - style = MaterialTheme.typography.h4.copy(fontWeight = FontWeight.Medium), - color = MaterialTheme.colors.onBackground + modifier = Modifier.padding(bottom = 2.dp), ) BacsMandateDetails( email = state.email, @@ -90,9 +94,15 @@ internal fun BacsMandateConfirmationFormView( isHtml = true ) Box(modifier = Modifier.weight(WEIGHT_40_PERCENT), contentAlignment = Alignment.CenterEnd) { + val tint = if (MaterialTheme.colors.surface.shouldUseDarkDynamicColor()) { + Color.Black + } else { + Color.White + } + Icon( painterResource(id = R.drawable.stripe_bacs_direct_debit_mark), - tint = MaterialTheme.stripeColors.subtitle, + tint = tint.copy(alpha = 0.5f), contentDescription = null ) } @@ -161,13 +171,13 @@ internal fun BacsMandateDetailsRow( Row(modifier = modifier, verticalAlignment = Alignment.CenterVertically) { Text( modifier = Modifier.weight(WEIGHT_40_PERCENT), - style = MaterialTheme.typography.subtitle2, + fontWeight = FontWeight(MaterialTheme.stripeTypography.fontWeightMedium), color = MaterialTheme.stripeColors.onComponent, text = label ) Text( modifier = Modifier.weight(WEIGHT_60_PERCENT), - style = MaterialTheme.typography.body1, + fontWeight = FontWeight(MaterialTheme.stripeTypography.fontWeightNormal), color = MaterialTheme.stripeColors.onComponent, text = value ) @@ -216,14 +226,14 @@ private fun MandateButtons( type = BacsMandateButtonType.Secondary, label = stringResource(R.string.stripe_paymentsheet_bacs_modify_details_button_label), onClick = { - viewActionHandler.invoke(BacsMandateConfirmationViewAction.OnCancelPressed) + viewActionHandler.invoke(BacsMandateConfirmationViewAction.OnModifyDetailsPressed) } ) } } @Composable -@Preview +@Preview(uiMode = UI_MODE_NIGHT_YES) private fun BacsMandateConfirmationFormPreview() { StripeTheme { BacsMandateConfirmationFormView( diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/paymentdatacollection/bacs/BacsMandateConfirmationLauncher.kt b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/paymentdatacollection/bacs/BacsMandateConfirmationLauncher.kt new file mode 100644 index 00000000000..c5d41042a28 --- /dev/null +++ b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/paymentdatacollection/bacs/BacsMandateConfirmationLauncher.kt @@ -0,0 +1,30 @@ +package com.stripe.android.paymentsheet.paymentdatacollection.bacs + +import androidx.activity.result.ActivityResultLauncher +import com.stripe.android.paymentsheet.PaymentSheet + +internal interface BacsMandateConfirmationLauncher { + fun launch( + data: BacsMandateData, + appearance: PaymentSheet.Appearance? + ) +} + +internal class DefaultBacsMandateConfirmationLauncher( + private val activityResultLauncher: ActivityResultLauncher +) : BacsMandateConfirmationLauncher { + override fun launch( + data: BacsMandateData, + appearance: PaymentSheet.Appearance? + ) { + activityResultLauncher.launch( + BacsMandateConfirmationContract.Args( + email = data.email, + nameOnAccount = data.name, + sortCode = data.sortCode, + accountNumber = data.accountNumber, + appearance = appearance + ) + ) + } +} diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/paymentdatacollection/bacs/BacsMandateConfirmationLauncherFactory.kt b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/paymentdatacollection/bacs/BacsMandateConfirmationLauncherFactory.kt new file mode 100644 index 00000000000..769792624d9 --- /dev/null +++ b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/paymentdatacollection/bacs/BacsMandateConfirmationLauncherFactory.kt @@ -0,0 +1,18 @@ +package com.stripe.android.paymentsheet.paymentdatacollection.bacs + +import androidx.activity.result.ActivityResultLauncher + +internal interface BacsMandateConfirmationLauncherFactory { + fun create( + activityResultLauncher: ActivityResultLauncher + ): BacsMandateConfirmationLauncher +} + +internal object DefaultBacsMandateConfirmationLauncherFactory : + BacsMandateConfirmationLauncherFactory { + override fun create( + activityResultLauncher: ActivityResultLauncher + ): BacsMandateConfirmationLauncher { + return DefaultBacsMandateConfirmationLauncher(activityResultLauncher) + } +} diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/paymentdatacollection/bacs/BacsMandateConfirmationResult.kt b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/paymentdatacollection/bacs/BacsMandateConfirmationResult.kt index 309a77e63a7..c580535a3fd 100644 --- a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/paymentdatacollection/bacs/BacsMandateConfirmationResult.kt +++ b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/paymentdatacollection/bacs/BacsMandateConfirmationResult.kt @@ -10,6 +10,9 @@ internal sealed interface BacsMandateConfirmationResult : Parcelable { @Parcelize object Confirmed : BacsMandateConfirmationResult + @Parcelize + object ModifyDetails : BacsMandateConfirmationResult + @Parcelize object Cancelled : BacsMandateConfirmationResult diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/paymentdatacollection/bacs/BacsMandateConfirmationViewAction.kt b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/paymentdatacollection/bacs/BacsMandateConfirmationViewAction.kt index 37bdd47b574..12636083f04 100644 --- a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/paymentdatacollection/bacs/BacsMandateConfirmationViewAction.kt +++ b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/paymentdatacollection/bacs/BacsMandateConfirmationViewAction.kt @@ -2,5 +2,6 @@ package com.stripe.android.paymentsheet.paymentdatacollection.bacs internal sealed interface BacsMandateConfirmationViewAction { object OnConfirmPressed : BacsMandateConfirmationViewAction - object OnCancelPressed : BacsMandateConfirmationViewAction + object OnModifyDetailsPressed : BacsMandateConfirmationViewAction + object OnBackPressed : BacsMandateConfirmationViewAction } diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/paymentdatacollection/bacs/BacsMandateConfirmationViewModel.kt b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/paymentdatacollection/bacs/BacsMandateConfirmationViewModel.kt index 2ba9b966dad..419bdbf32d7 100644 --- a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/paymentdatacollection/bacs/BacsMandateConfirmationViewModel.kt +++ b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/paymentdatacollection/bacs/BacsMandateConfirmationViewModel.kt @@ -37,7 +37,8 @@ internal class BacsMandateConfirmationViewModel constructor( fun handleViewAction(action: BacsMandateConfirmationViewAction) { when (action) { is BacsMandateConfirmationViewAction.OnConfirmPressed -> onConfirmPress() - is BacsMandateConfirmationViewAction.OnCancelPressed -> onCancelPress() + is BacsMandateConfirmationViewAction.OnModifyDetailsPressed -> onModifyDetailsPressed() + is BacsMandateConfirmationViewAction.OnBackPressed -> onBackPress() } } @@ -47,7 +48,13 @@ internal class BacsMandateConfirmationViewModel constructor( } } - private fun onCancelPress() { + private fun onModifyDetailsPressed() { + viewModelScope.launch { + _result.emit(BacsMandateConfirmationResult.ModifyDetails) + } + } + + private fun onBackPress() { viewModelScope.launch { _result.emit(BacsMandateConfirmationResult.Cancelled) } diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/paymentdatacollection/bacs/BacsMandateData.kt b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/paymentdatacollection/bacs/BacsMandateData.kt new file mode 100644 index 00000000000..e0d147d2a86 --- /dev/null +++ b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/paymentdatacollection/bacs/BacsMandateData.kt @@ -0,0 +1,34 @@ +package com.stripe.android.paymentsheet.paymentdatacollection.bacs + +import com.stripe.android.model.PaymentMethodCreateParams +import com.stripe.android.paymentsheet.model.PaymentSelection + +internal data class BacsMandateData( + val name: String, + val email: String, + val accountNumber: String, + val sortCode: String +) { + companion object { + fun fromPaymentSelection( + paymentSelection: PaymentSelection.New.GenericPaymentMethod + ): BacsMandateData? { + val overrideParams = paymentSelection.paymentMethodCreateParams + + val bacsDebit = PaymentMethodCreateParams.createBacsFromParams(overrideParams) + val name = PaymentMethodCreateParams.getNameFromParams(overrideParams) + val email = PaymentMethodCreateParams.getEmailFromParams(overrideParams) + + return if (bacsDebit != null && name != null && email != null) { + BacsMandateData( + name = name, + email = email, + accountNumber = bacsDebit.accountNumber, + sortCode = bacsDebit.sortCode + ) + } else { + null + } + } + } +} diff --git a/paymentsheet/src/test/java/com/stripe/android/model/PaymentMethodCreateParamsFixtures.kt b/paymentsheet/src/test/java/com/stripe/android/model/PaymentMethodCreateParamsFixtures.kt index 14926150aa2..a375266d45d 100644 --- a/paymentsheet/src/test/java/com/stripe/android/model/PaymentMethodCreateParamsFixtures.kt +++ b/paymentsheet/src/test/java/com/stripe/android/model/PaymentMethodCreateParamsFixtures.kt @@ -64,14 +64,6 @@ internal object PaymentMethodCreateParamsFixtures { BILLING_DETAILS ) - internal val BACS_DEBIT = PaymentMethodCreateParams.create( - bacsDebit = PaymentMethodCreateParams.BacsDebit( - accountNumber = "00012345", - sortCode = "108800" - ), - billingDetails = BILLING_DETAILS - ) - internal val SOFORT = PaymentMethodCreateParams.create( sofort = PaymentMethodCreateParams.Sofort( country = "DE" diff --git a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/PaymentSheetActivityTest.kt b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/PaymentSheetActivityTest.kt index 5761887cc3a..f7911906f45 100644 --- a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/PaymentSheetActivityTest.kt +++ b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/PaymentSheetActivityTest.kt @@ -49,6 +49,7 @@ import com.stripe.android.paymentsheet.model.PaymentSheetViewState import com.stripe.android.paymentsheet.navigation.PaymentSheetScreen import com.stripe.android.paymentsheet.navigation.PaymentSheetScreen.AddAnotherPaymentMethod import com.stripe.android.paymentsheet.navigation.PaymentSheetScreen.SelectSavedPaymentMethods +import com.stripe.android.paymentsheet.paymentdatacollection.bacs.FakeBacsMandateConfirmationLauncherFactory import com.stripe.android.paymentsheet.state.LinkState import com.stripe.android.paymentsheet.ui.GooglePayButton import com.stripe.android.paymentsheet.ui.PAYMENT_SHEET_PRIMARY_BUTTON_TEST_TAG @@ -1001,6 +1002,7 @@ internal class PaymentSheetActivityTest { lpmRepository, stripePaymentLauncherAssistedFactory, googlePayPaymentMethodLauncherFactory, + FakeBacsMandateConfirmationLauncherFactory(), Logger.noop(), testDispatcher, savedStateHandle = savedStateHandle, 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 073e8b7bac4..323fc366049 100644 --- a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/PaymentSheetViewModelTest.kt +++ b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/PaymentSheetViewModelTest.kt @@ -3,6 +3,7 @@ package com.stripe.android.paymentsheet import android.app.Application import androidx.activity.result.ActivityResultCallback import androidx.activity.result.ActivityResultCaller +import androidx.activity.result.ActivityResultLauncher import androidx.arch.core.executor.testing.InstantTaskExecutorRule import androidx.lifecycle.testing.TestLifecycleOwner import androidx.test.core.app.ApplicationProvider @@ -54,6 +55,10 @@ import com.stripe.android.paymentsheet.navigation.PaymentSheetScreen.AddAnotherP import com.stripe.android.paymentsheet.navigation.PaymentSheetScreen.AddFirstPaymentMethod import com.stripe.android.paymentsheet.navigation.PaymentSheetScreen.SelectSavedPaymentMethods import com.stripe.android.paymentsheet.paymentdatacollection.ach.USBankAccountFormScreenState +import com.stripe.android.paymentsheet.paymentdatacollection.bacs.BacsMandateConfirmationContract +import com.stripe.android.paymentsheet.paymentdatacollection.bacs.BacsMandateData +import com.stripe.android.paymentsheet.paymentdatacollection.bacs.FakeBacsMandateConfirmationLauncher +import com.stripe.android.paymentsheet.paymentdatacollection.bacs.FakeBacsMandateConfirmationLauncherFactory import com.stripe.android.paymentsheet.repositories.CustomerRepository import com.stripe.android.paymentsheet.state.GooglePayState import com.stripe.android.paymentsheet.state.LinkState @@ -1689,6 +1694,61 @@ internal class PaymentSheetViewModelTest { ) } + @Test + fun `Launch confirmation form when Bacs debit is selected and filled`() { + val expectedAccountNumber = "00012345" + val expectedSortCode = "108800" + val expectedName = "John Doe" + val expectedEmail = "johndoe@email.com" + + val launcher = spy(FakeBacsMandateConfirmationLauncher()) + val launcherFactory = spy(FakeBacsMandateConfirmationLauncherFactory(launcher)) + + val viewModel = createViewModel( + bacsMandateConfirmationLauncherFactory = launcherFactory + ) + + val activityResultLauncherMock = mock>() + + viewModel.setupBacsMandateConfirmation(activityResultLauncherMock) + + verify(launcherFactory).create(activityResultLauncherMock) + + viewModel.updateSelection( + PaymentSelection.New.GenericPaymentMethod( + labelResource = "Test", + iconResource = 0, + paymentMethodCreateParams = PaymentMethodCreateParams.Companion.create( + bacsDebit = PaymentMethodCreateParams.BacsDebit( + accountNumber = expectedAccountNumber, + sortCode = expectedSortCode + ), + billingDetails = PaymentMethod.BillingDetails( + name = expectedName, + email = expectedEmail + ) + ), + customerRequestedSave = PaymentSelection.CustomerRequestedSave.NoRequest, + lightThemeIconUrl = null, + darkThemeIconUrl = null, + ) + ) + + viewModel.checkout() + + verify(launcher).launch( + eq( + BacsMandateData( + name = expectedName, + email = expectedEmail, + sortCode = expectedSortCode, + accountNumber = expectedAccountNumber, + ) + ), + eq(PaymentSheet.Appearance()) + ) + } + @Test fun `On complete payment launcher result in PI mode & should reuse, should save payment selection`() = runTest { selectionSavedTest( @@ -1845,6 +1905,8 @@ internal class PaymentSheetViewModelTest { isGooglePayReady: Boolean = false, delay: Duration = Duration.ZERO, lpmRepository: LpmRepository = this.lpmRepository, + bacsMandateConfirmationLauncherFactory: FakeBacsMandateConfirmationLauncherFactory = + FakeBacsMandateConfirmationLauncherFactory(), ): PaymentSheetViewModel { val paymentConfiguration = PaymentConfiguration(ApiKeyFixtures.FAKE_PUBLISHABLE_KEY) return TestViewModelFactory.create( @@ -1868,6 +1930,7 @@ internal class PaymentSheetViewModelTest { lpmRepository = lpmRepository, paymentLauncherFactory = paymentLauncherFactory, googlePayPaymentMethodLauncherFactory = googlePayLauncherFactory, + bacsMandateConfirmationLauncherFactory = bacsMandateConfirmationLauncherFactory, logger = Logger.noop(), workContext = testDispatcher, savedStateHandle = savedStateHandle, diff --git a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/flowcontroller/DefaultFlowControllerTest.kt b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/flowcontroller/DefaultFlowControllerTest.kt index e3e6fb2a64a..a411f912564 100644 --- a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/flowcontroller/DefaultFlowControllerTest.kt +++ b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/flowcontroller/DefaultFlowControllerTest.kt @@ -61,6 +61,11 @@ import com.stripe.android.paymentsheet.model.PaymentOption import com.stripe.android.paymentsheet.model.PaymentOptionFactory import com.stripe.android.paymentsheet.model.PaymentSelection import com.stripe.android.paymentsheet.model.SavedSelection +import com.stripe.android.paymentsheet.paymentdatacollection.bacs.BacsMandateConfirmationContract +import com.stripe.android.paymentsheet.paymentdatacollection.bacs.BacsMandateConfirmationLauncherFactory +import com.stripe.android.paymentsheet.paymentdatacollection.bacs.BacsMandateData +import com.stripe.android.paymentsheet.paymentdatacollection.bacs.FakeBacsMandateConfirmationLauncher +import com.stripe.android.paymentsheet.paymentdatacollection.bacs.FakeBacsMandateConfirmationLauncherFactory import com.stripe.android.paymentsheet.state.LinkState import com.stripe.android.paymentsheet.state.PaymentSheetLoader import com.stripe.android.paymentsheet.state.PaymentSheetState @@ -90,6 +95,7 @@ import org.mockito.kotlin.eq import org.mockito.kotlin.isA import org.mockito.kotlin.isNull import org.mockito.kotlin.mock +import org.mockito.kotlin.spy import org.mockito.kotlin.verify import org.mockito.kotlin.verifyNoMoreInteractions import org.mockito.kotlin.whenever @@ -202,6 +208,14 @@ internal class DefaultFlowControllerTest { ) ).thenReturn(mock()) + whenever( + activityResultRegistry.register( + any(), + any(), + any() + ) + ).thenReturn(mock()) + whenever(paymentLauncherAssistedFactory.create(any(), any(), anyOrNull(), any(), any())) .thenReturn(paymentLauncher) @@ -1527,6 +1541,73 @@ internal class DefaultFlowControllerTest { ) } + @Test + fun `Launches Bacs with name, email, sort code and account number`() = runTest { + val expectedAccountNumber = "00012345" + val expectedSortCode = "108800" + val expectedName = "John Doe" + val expectedEmail = "johndoe@email.com" + + val activityResultLauncherMock = mock>() + + whenever( + activityResultRegistry.register( + any(), + any(), + any() + ) + ).thenReturn(activityResultLauncherMock) + + val launcher = spy(FakeBacsMandateConfirmationLauncher()) + val launcherFactory = spy(FakeBacsMandateConfirmationLauncherFactory(launcher)) + + val flowController = createFlowController( + bacsMandateConfirmationLauncherFactory = launcherFactory + ) + + verify(launcherFactory).create(activityResultLauncherMock) + + flowController.configureExpectingSuccess( + clientSecret = PaymentSheetFixtures.SETUP_CLIENT_SECRET + ) + + flowController.onPaymentOptionResult( + PaymentOptionResult.Succeeded( + PaymentSelection.New.GenericPaymentMethod( + labelResource = "", + iconResource = 0, + lightThemeIconUrl = null, + darkThemeIconUrl = null, + customerRequestedSave = PaymentSelection.CustomerRequestedSave.NoRequest, + paymentMethodCreateParams = PaymentMethodCreateParams.Companion.create( + bacsDebit = PaymentMethodCreateParams.BacsDebit( + accountNumber = expectedAccountNumber, + sortCode = expectedSortCode + ), + billingDetails = PaymentMethod.BillingDetails( + name = expectedName, + email = expectedEmail + ) + ) + ) + ) + ) + + flowController.confirm() + + verify(launcher).launch( + eq( + BacsMandateData( + name = expectedName, + email = expectedEmail, + sortCode = expectedSortCode, + accountNumber = expectedAccountNumber + ) + ), + eq(PaymentSheet.Appearance()) + ) + } + @Test fun `On complete internal payment result in PI mode & should reuse, should save payment selection`() = runTest { selectionSavedTest( @@ -1676,6 +1757,8 @@ internal class DefaultFlowControllerTest { loginState = LinkState.LoginState.LoggedIn, ), viewModel: FlowControllerViewModel = createViewModel(), + bacsMandateConfirmationLauncherFactory: BacsMandateConfirmationLauncherFactory = + FakeBacsMandateConfirmationLauncherFactory() ): DefaultFlowController { return createFlowController( FakePaymentSheetLoader( @@ -1684,13 +1767,16 @@ internal class DefaultFlowControllerTest { paymentSelection = paymentSelection, linkState = linkState, ), - viewModel + viewModel, + bacsMandateConfirmationLauncherFactory ) } private fun createFlowController( paymentSheetLoader: PaymentSheetLoader, viewModel: FlowControllerViewModel = createViewModel(), + bacsMandateConfirmationLauncherFactory: BacsMandateConfirmationLauncherFactory = + FakeBacsMandateConfirmationLauncherFactory() ) = DefaultFlowController( viewModelScope = testScope, lifecycleOwner = lifeCycleOwner, @@ -1713,6 +1799,7 @@ internal class DefaultFlowControllerTest { productUsage = PRODUCT_USAGE, googlePayPaymentMethodLauncherFactory = createGooglePayPaymentMethodLauncherFactory(), prefsRepositoryFactory = { prefsRepository }, + bacsMandateConfirmationLauncherFactory = bacsMandateConfirmationLauncherFactory, linkLauncher = linkPaymentLauncher, configurationHandler = FlowControllerConfigurationHandler( paymentSheetLoader = paymentSheetLoader, diff --git a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/paymentdatacollection/bacs/BacsMandateConfirmationViewModelTest.kt b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/paymentdatacollection/bacs/BacsMandateConfirmationViewModelTest.kt index 5800b192c4f..101f3aaa349 100644 --- a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/paymentdatacollection/bacs/BacsMandateConfirmationViewModelTest.kt +++ b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/paymentdatacollection/bacs/BacsMandateConfirmationViewModelTest.kt @@ -67,7 +67,7 @@ class BacsMandateConfirmationViewModelTest { val viewModel = createViewModel() viewModel.result.test { - viewModel.handleViewAction(BacsMandateConfirmationViewAction.OnCancelPressed) + viewModel.handleViewAction(BacsMandateConfirmationViewAction.OnBackPressed) assertThat(awaitItem()).isEqualTo(BacsMandateConfirmationResult.Cancelled) } diff --git a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/paymentdatacollection/bacs/BacsMandateDataTest.kt b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/paymentdatacollection/bacs/BacsMandateDataTest.kt new file mode 100644 index 00000000000..e7971860610 --- /dev/null +++ b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/paymentdatacollection/bacs/BacsMandateDataTest.kt @@ -0,0 +1,76 @@ +package com.stripe.android.paymentsheet.paymentdatacollection.bacs + +import com.google.common.truth.Truth.assertThat +import com.stripe.android.model.PaymentMethod +import com.stripe.android.model.PaymentMethodCreateParams +import com.stripe.android.paymentsheet.model.PaymentSelection +import org.junit.Test + +class BacsMandateDataTest { + @Test + fun `when payment selection is Bacs and name & email are provided, 'fromPaymentSelection' should return data`() { + val selection = PaymentSelection.New.GenericPaymentMethod( + labelResource = "", + iconResource = 0, + customerRequestedSave = PaymentSelection.CustomerRequestedSave.NoRequest, + lightThemeIconUrl = null, + darkThemeIconUrl = null, + paymentMethodCreateParams = PaymentMethodCreateParams.Companion.create( + bacsDebit = PaymentMethodCreateParams.BacsDebit( + accountNumber = "00012345", + sortCode = "10-88-00" + ), + billingDetails = PaymentMethod.BillingDetails( + name = "John Doe", + email = "johndoe@email.com" + ) + ) + ) + + assertThat(BacsMandateData.fromPaymentSelection(selection)).isEqualTo( + BacsMandateData( + name = "John Doe", + email = "johndoe@email.com", + accountNumber = "00012345", + sortCode = "10-88-00" + ) + ) + } + + @Test + fun `when payment selection is Bacs but without name or email, 'fromPaymentSelection' should return null`() { + val selection = PaymentSelection.New.GenericPaymentMethod( + labelResource = "", + iconResource = 0, + customerRequestedSave = PaymentSelection.CustomerRequestedSave.NoRequest, + lightThemeIconUrl = null, + darkThemeIconUrl = null, + paymentMethodCreateParams = PaymentMethodCreateParams.Companion.create( + bacsDebit = PaymentMethodCreateParams.BacsDebit( + accountNumber = "00012345", + sortCode = "10-88-00" + ), + billingDetails = PaymentMethod.BillingDetails() + ) + ) + + assertThat(BacsMandateData.fromPaymentSelection(selection)).isNull() + } + + @Test + fun `when payment selection is not Bacs, 'fromPaymentSelection' should return null`() { + val selection = PaymentSelection.New.GenericPaymentMethod( + labelResource = "", + iconResource = 0, + customerRequestedSave = PaymentSelection.CustomerRequestedSave.NoRequest, + lightThemeIconUrl = null, + darkThemeIconUrl = null, + paymentMethodCreateParams = PaymentMethodCreateParams.Companion.create( + card = PaymentMethodCreateParams.Card(), + billingDetails = PaymentMethod.BillingDetails() + ) + ) + + assertThat(BacsMandateData.fromPaymentSelection(selection)).isNull() + } +} diff --git a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/paymentdatacollection/bacs/FakeBacsMandateConfirmationLauncher.kt b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/paymentdatacollection/bacs/FakeBacsMandateConfirmationLauncher.kt new file mode 100644 index 00000000000..ef65e80f126 --- /dev/null +++ b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/paymentdatacollection/bacs/FakeBacsMandateConfirmationLauncher.kt @@ -0,0 +1,12 @@ +package com.stripe.android.paymentsheet.paymentdatacollection.bacs + +import com.stripe.android.paymentsheet.PaymentSheet + +internal class FakeBacsMandateConfirmationLauncher : BacsMandateConfirmationLauncher { + override fun launch( + data: BacsMandateData, + appearance: PaymentSheet.Appearance? + ) { + // No-op + } +} diff --git a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/paymentdatacollection/bacs/FakeBacsMandateConfirmationLauncherFactory.kt b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/paymentdatacollection/bacs/FakeBacsMandateConfirmationLauncherFactory.kt new file mode 100644 index 00000000000..fc40cbb1c63 --- /dev/null +++ b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/paymentdatacollection/bacs/FakeBacsMandateConfirmationLauncherFactory.kt @@ -0,0 +1,13 @@ +package com.stripe.android.paymentsheet.paymentdatacollection.bacs + +import androidx.activity.result.ActivityResultLauncher + +internal class FakeBacsMandateConfirmationLauncherFactory( + private val launcher: BacsMandateConfirmationLauncher = FakeBacsMandateConfirmationLauncher() +) : BacsMandateConfirmationLauncherFactory { + override fun create( + activityResultLauncher: ActivityResultLauncher + ): BacsMandateConfirmationLauncher { + return launcher + } +} diff --git a/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.paymentdatacollection.bacs_BacsMandateConfirmationFormScreenshotTest_testDetailsRow[DarkTheme,CrazyAppearance,DefaultFont].png b/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.paymentdatacollection.bacs_BacsMandateConfirmationFormScreenshotTest_testDetailsRow[DarkTheme,CrazyAppearance,DefaultFont].png index 228d8c70e9c..f2d4921a051 100644 Binary files a/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.paymentdatacollection.bacs_BacsMandateConfirmationFormScreenshotTest_testDetailsRow[DarkTheme,CrazyAppearance,DefaultFont].png and b/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.paymentdatacollection.bacs_BacsMandateConfirmationFormScreenshotTest_testDetailsRow[DarkTheme,CrazyAppearance,DefaultFont].png differ diff --git a/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.paymentdatacollection.bacs_BacsMandateConfirmationFormScreenshotTest_testDetailsRow[DarkTheme,CrazyAppearance,LargeFont].png b/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.paymentdatacollection.bacs_BacsMandateConfirmationFormScreenshotTest_testDetailsRow[DarkTheme,CrazyAppearance,LargeFont].png index 4865d7cffc3..794bc0d9ad9 100644 Binary files a/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.paymentdatacollection.bacs_BacsMandateConfirmationFormScreenshotTest_testDetailsRow[DarkTheme,CrazyAppearance,LargeFont].png and b/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.paymentdatacollection.bacs_BacsMandateConfirmationFormScreenshotTest_testDetailsRow[DarkTheme,CrazyAppearance,LargeFont].png differ diff --git a/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.paymentdatacollection.bacs_BacsMandateConfirmationFormScreenshotTest_testDetailsRow[DarkTheme,CustomAppearance,DefaultFont].png b/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.paymentdatacollection.bacs_BacsMandateConfirmationFormScreenshotTest_testDetailsRow[DarkTheme,CustomAppearance,DefaultFont].png index dac05b8c98b..b7e00271604 100644 Binary files a/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.paymentdatacollection.bacs_BacsMandateConfirmationFormScreenshotTest_testDetailsRow[DarkTheme,CustomAppearance,DefaultFont].png and b/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.paymentdatacollection.bacs_BacsMandateConfirmationFormScreenshotTest_testDetailsRow[DarkTheme,CustomAppearance,DefaultFont].png differ diff --git a/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.paymentdatacollection.bacs_BacsMandateConfirmationFormScreenshotTest_testDetailsRow[DarkTheme,CustomAppearance,LargeFont].png b/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.paymentdatacollection.bacs_BacsMandateConfirmationFormScreenshotTest_testDetailsRow[DarkTheme,CustomAppearance,LargeFont].png index 774d2acda0c..242405418e3 100644 Binary files a/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.paymentdatacollection.bacs_BacsMandateConfirmationFormScreenshotTest_testDetailsRow[DarkTheme,CustomAppearance,LargeFont].png and b/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.paymentdatacollection.bacs_BacsMandateConfirmationFormScreenshotTest_testDetailsRow[DarkTheme,CustomAppearance,LargeFont].png differ diff --git a/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.paymentdatacollection.bacs_BacsMandateConfirmationFormScreenshotTest_testDetailsRow[DarkTheme,DefaultAppearance,DefaultFont].png b/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.paymentdatacollection.bacs_BacsMandateConfirmationFormScreenshotTest_testDetailsRow[DarkTheme,DefaultAppearance,DefaultFont].png index dac05b8c98b..b7e00271604 100644 Binary files a/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.paymentdatacollection.bacs_BacsMandateConfirmationFormScreenshotTest_testDetailsRow[DarkTheme,DefaultAppearance,DefaultFont].png and b/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.paymentdatacollection.bacs_BacsMandateConfirmationFormScreenshotTest_testDetailsRow[DarkTheme,DefaultAppearance,DefaultFont].png differ diff --git a/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.paymentdatacollection.bacs_BacsMandateConfirmationFormScreenshotTest_testDetailsRow[DarkTheme,DefaultAppearance,LargeFont].png b/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.paymentdatacollection.bacs_BacsMandateConfirmationFormScreenshotTest_testDetailsRow[DarkTheme,DefaultAppearance,LargeFont].png index 774d2acda0c..242405418e3 100644 Binary files a/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.paymentdatacollection.bacs_BacsMandateConfirmationFormScreenshotTest_testDetailsRow[DarkTheme,DefaultAppearance,LargeFont].png and b/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.paymentdatacollection.bacs_BacsMandateConfirmationFormScreenshotTest_testDetailsRow[DarkTheme,DefaultAppearance,LargeFont].png differ diff --git a/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.paymentdatacollection.bacs_BacsMandateConfirmationFormScreenshotTest_testDetailsRow[LightTheme,CrazyAppearance,DefaultFont].png b/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.paymentdatacollection.bacs_BacsMandateConfirmationFormScreenshotTest_testDetailsRow[LightTheme,CrazyAppearance,DefaultFont].png index 228d8c70e9c..f2d4921a051 100644 Binary files a/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.paymentdatacollection.bacs_BacsMandateConfirmationFormScreenshotTest_testDetailsRow[LightTheme,CrazyAppearance,DefaultFont].png and b/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.paymentdatacollection.bacs_BacsMandateConfirmationFormScreenshotTest_testDetailsRow[LightTheme,CrazyAppearance,DefaultFont].png differ diff --git a/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.paymentdatacollection.bacs_BacsMandateConfirmationFormScreenshotTest_testDetailsRow[LightTheme,CrazyAppearance,LargeFont].png b/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.paymentdatacollection.bacs_BacsMandateConfirmationFormScreenshotTest_testDetailsRow[LightTheme,CrazyAppearance,LargeFont].png index 4865d7cffc3..794bc0d9ad9 100644 Binary files a/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.paymentdatacollection.bacs_BacsMandateConfirmationFormScreenshotTest_testDetailsRow[LightTheme,CrazyAppearance,LargeFont].png and b/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.paymentdatacollection.bacs_BacsMandateConfirmationFormScreenshotTest_testDetailsRow[LightTheme,CrazyAppearance,LargeFont].png differ diff --git a/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.paymentdatacollection.bacs_BacsMandateConfirmationFormScreenshotTest_testDetailsRow[LightTheme,CustomAppearance,DefaultFont].png b/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.paymentdatacollection.bacs_BacsMandateConfirmationFormScreenshotTest_testDetailsRow[LightTheme,CustomAppearance,DefaultFont].png index 30302899450..cbebeff1c05 100644 Binary files a/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.paymentdatacollection.bacs_BacsMandateConfirmationFormScreenshotTest_testDetailsRow[LightTheme,CustomAppearance,DefaultFont].png and b/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.paymentdatacollection.bacs_BacsMandateConfirmationFormScreenshotTest_testDetailsRow[LightTheme,CustomAppearance,DefaultFont].png differ diff --git a/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.paymentdatacollection.bacs_BacsMandateConfirmationFormScreenshotTest_testDetailsRow[LightTheme,CustomAppearance,LargeFont].png b/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.paymentdatacollection.bacs_BacsMandateConfirmationFormScreenshotTest_testDetailsRow[LightTheme,CustomAppearance,LargeFont].png index 7ed13d0350e..90b7cb73051 100644 Binary files a/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.paymentdatacollection.bacs_BacsMandateConfirmationFormScreenshotTest_testDetailsRow[LightTheme,CustomAppearance,LargeFont].png and b/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.paymentdatacollection.bacs_BacsMandateConfirmationFormScreenshotTest_testDetailsRow[LightTheme,CustomAppearance,LargeFont].png differ diff --git a/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.paymentdatacollection.bacs_BacsMandateConfirmationFormScreenshotTest_testDetailsRow[LightTheme,DefaultAppearance,DefaultFont].png b/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.paymentdatacollection.bacs_BacsMandateConfirmationFormScreenshotTest_testDetailsRow[LightTheme,DefaultAppearance,DefaultFont].png index 30302899450..cbebeff1c05 100644 Binary files a/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.paymentdatacollection.bacs_BacsMandateConfirmationFormScreenshotTest_testDetailsRow[LightTheme,DefaultAppearance,DefaultFont].png and b/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.paymentdatacollection.bacs_BacsMandateConfirmationFormScreenshotTest_testDetailsRow[LightTheme,DefaultAppearance,DefaultFont].png differ diff --git a/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.paymentdatacollection.bacs_BacsMandateConfirmationFormScreenshotTest_testDetailsRow[LightTheme,DefaultAppearance,LargeFont].png b/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.paymentdatacollection.bacs_BacsMandateConfirmationFormScreenshotTest_testDetailsRow[LightTheme,DefaultAppearance,LargeFont].png index 7ed13d0350e..90b7cb73051 100644 Binary files a/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.paymentdatacollection.bacs_BacsMandateConfirmationFormScreenshotTest_testDetailsRow[LightTheme,DefaultAppearance,LargeFont].png and b/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.paymentdatacollection.bacs_BacsMandateConfirmationFormScreenshotTest_testDetailsRow[LightTheme,DefaultAppearance,LargeFont].png differ diff --git a/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.paymentdatacollection.bacs_BacsMandateConfirmationFormScreenshotTest_testDetails[LightTheme,DefaultAppearance,LargeFont].png b/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.paymentdatacollection.bacs_BacsMandateConfirmationFormScreenshotTest_testDetails[LightTheme,DefaultAppearance,LargeFont].png index 8fa803e9c9f..27151b6542d 100644 Binary files a/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.paymentdatacollection.bacs_BacsMandateConfirmationFormScreenshotTest_testDetails[LightTheme,DefaultAppearance,LargeFont].png and b/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.paymentdatacollection.bacs_BacsMandateConfirmationFormScreenshotTest_testDetails[LightTheme,DefaultAppearance,LargeFont].png differ diff --git a/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.paymentdatacollection.bacs_BacsMandateConfirmationFormScreenshotTest_testFullForm[LightTheme,DefaultAppearance,LargeFont].png b/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.paymentdatacollection.bacs_BacsMandateConfirmationFormScreenshotTest_testFullForm[LightTheme,DefaultAppearance,LargeFont].png index cfdea87b0c9..ea4cf7dce43 100644 Binary files a/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.paymentdatacollection.bacs_BacsMandateConfirmationFormScreenshotTest_testFullForm[LightTheme,DefaultAppearance,LargeFont].png and b/paymentsheet/src/test/snapshots/images/com.stripe.android.paymentsheet.paymentdatacollection.bacs_BacsMandateConfirmationFormScreenshotTest_testFullForm[LightTheme,DefaultAppearance,LargeFont].png differ diff --git a/stripe-ui-core/src/main/java/com/stripe/android/uicore/elements/CheckboxFieldUI.kt b/stripe-ui-core/src/main/java/com/stripe/android/uicore/elements/CheckboxFieldUI.kt index 2a08696bfcc..4e00e9658e3 100644 --- a/stripe-ui-core/src/main/java/com/stripe/android/uicore/elements/CheckboxFieldUI.kt +++ b/stripe-ui-core/src/main/java/com/stripe/android/uicore/elements/CheckboxFieldUI.kt @@ -23,11 +23,11 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.R import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.stateDescription -import androidx.compose.ui.semantics.testTag import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.stripe.android.uicore.StripeTheme @@ -99,11 +99,9 @@ internal fun CheckboxFieldUIView( } Column( - modifier = modifier - .semantics { - testTag = debugTag - stateDescription = accessibilityDescription - } + modifier = modifier.semantics { + stateDescription = accessibilityDescription + } ) { Row( modifier = Modifier @@ -113,6 +111,7 @@ internal fun CheckboxFieldUIView( onValueChange = onValueChange, enabled = enabled ) + .testTag(debugTag) .fillMaxWidth(), ) { Checkbox(