From 865db79224f37b1fcca612f30df3574c932ead3d Mon Sep 17 00:00:00 2001 From: Samer Alabi <141707240+samer-stripe@users.noreply.github.com> Date: Mon, 2 Dec 2024 10:32:57 -0500 Subject: [PATCH] Inject `ConfirmationHandler` into `FlowController` (#9651) --- .../paymentsheet/PaymentOptionContract.kt | 2 - .../flowcontroller/DefaultFlowController.kt | 41 +------------------ .../flowcontroller/FlowControllerComponent.kt | 3 -- .../flowcontroller/FlowControllerModule.kt | 25 +++++++++++ .../FlowControllerStateComponent.kt | 9 ++++ .../flowcontroller/FlowControllerViewModel.kt | 21 ++++++++++ .../PaymentOptionsViewModelTest.kt | 1 - .../paymentsheet/PaymentSheetFixtures.kt | 1 - .../DefaultFlowControllerTest.kt | 26 +++++++----- .../FlowControllerConfigurationHandlerTest.kt | 3 +- 10 files changed, 74 insertions(+), 58 deletions(-) diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/PaymentOptionContract.kt b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/PaymentOptionContract.kt index 6544be64b7e..554aa3df078 100644 --- a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/PaymentOptionContract.kt +++ b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/PaymentOptionContract.kt @@ -3,7 +3,6 @@ package com.stripe.android.paymentsheet import android.content.Context import android.content.Intent import androidx.activity.result.contract.ActivityResultContract -import androidx.annotation.ColorInt import com.stripe.android.paymentsheet.state.PaymentSheetState import com.stripe.android.view.ActivityStarter import kotlinx.parcelize.Parcelize @@ -29,7 +28,6 @@ internal class PaymentOptionContract : internal data class Args( val state: PaymentSheetState.Full, val configuration: PaymentSheet.Configuration, - @ColorInt val statusBarColor: Int?, val enableLogging: Boolean, val productUsage: Set ) : ActivityStarter.Args { 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 f8cef7b342c..c05cee16962 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 @@ -9,30 +9,23 @@ import androidx.annotation.VisibleForTesting import androidx.core.app.ActivityOptionsCompat import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.SavedStateViewModelFactory import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelStoreOwner import androidx.lifecycle.lifecycleScope -import com.stripe.android.PaymentConfiguration import com.stripe.android.common.exception.stripeErrorMessage import com.stripe.android.core.exception.StripeException import com.stripe.android.core.injection.ENABLE_LOGGING -import com.stripe.android.core.injection.IOContext -import com.stripe.android.core.utils.UserFacingLogger -import com.stripe.android.googlepaylauncher.injection.GooglePayPaymentMethodLauncherFactory import com.stripe.android.link.LinkActivityResult import com.stripe.android.link.LinkPaymentLauncher import com.stripe.android.model.PaymentMethod import com.stripe.android.model.PaymentMethodOptionsParams import com.stripe.android.paymentelement.confirmation.ConfirmationHandler -import com.stripe.android.paymentelement.confirmation.DefaultConfirmationHandler import com.stripe.android.paymentelement.confirmation.intent.DeferredIntentConfirmationType import com.stripe.android.paymentelement.confirmation.intent.IntentConfirmationInterceptor import com.stripe.android.paymentelement.confirmation.toConfirmationOption import com.stripe.android.payments.core.analytics.ErrorReporter import com.stripe.android.payments.core.injection.PRODUCT_USAGE import com.stripe.android.payments.paymentlauncher.PaymentResult -import com.stripe.android.payments.paymentlauncher.StripePaymentLauncherAssistedFactory import com.stripe.android.paymentsheet.ExternalPaymentMethodInterceptor import com.stripe.android.paymentsheet.InitializedViaCompose import com.stripe.android.paymentsheet.PaymentOptionCallback @@ -50,7 +43,6 @@ 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.isLink -import com.stripe.android.paymentsheet.paymentdatacollection.bacs.BacsMandateConfirmationLauncherFactory import com.stripe.android.paymentsheet.paymentdatacollection.cvcrecollection.CvcRecollectionContract import com.stripe.android.paymentsheet.paymentdatacollection.cvcrecollection.CvcRecollectionLauncher import com.stripe.android.paymentsheet.paymentdatacollection.cvcrecollection.CvcRecollectionLauncherFactory @@ -62,25 +54,20 @@ import com.stripe.android.paymentsheet.ui.SepaMandateContract import com.stripe.android.paymentsheet.ui.SepaMandateResult import com.stripe.android.paymentsheet.utils.canSave import com.stripe.android.uicore.utils.AnimationConstants -import dagger.Lazy import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch -import kotlinx.coroutines.plus import kotlinx.parcelize.Parcelize import javax.inject.Inject import javax.inject.Named -import javax.inject.Provider -import kotlin.coroutines.CoroutineContext @FlowControllerScope internal class DefaultFlowController @Inject internal constructor( // Properties provided through FlowControllerComponent.Builder private val viewModelScope: CoroutineScope, private val lifecycleOwner: LifecycleOwner, - private val statusBarColor: () -> Int?, private val paymentOptionFactory: PaymentOptionFactory, private val paymentOptionCallback: PaymentOptionCallback, private val paymentResultCallback: PaymentSheetResultCallback, @@ -90,24 +77,14 @@ internal class DefaultFlowController @Inject internal constructor( private val context: Context, private val eventReporter: EventReporter, private val viewModel: FlowControllerViewModel, - paymentLauncherFactory: StripePaymentLauncherAssistedFactory, - /** - * [PaymentConfiguration] is [Lazy] because the client might set publishableKey and - * stripeAccountId after creating a [DefaultFlowController]. - */ - lazyPaymentConfiguration: Provider, + private val confirmationHandler: ConfirmationHandler, @Named(ENABLE_LOGGING) private val enableLogging: Boolean, @Named(PRODUCT_USAGE) private val productUsage: Set, - googlePayPaymentMethodLauncherFactory: GooglePayPaymentMethodLauncherFactory, - bacsMandateConfirmationLauncherFactory: BacsMandateConfirmationLauncherFactory, cvcRecollectionLauncherFactory: CvcRecollectionLauncherFactory, private val linkLauncher: LinkPaymentLauncher, private val configurationHandler: FlowControllerConfigurationHandler, - intentConfirmationInterceptor: IntentConfirmationInterceptor, private val errorReporter: ErrorReporter, @InitializedViaCompose private val initializedViaCompose: Boolean, - @IOContext workContext: CoroutineContext, - logger: UserFacingLogger, private val cvcRecollectionHandler: CvcRecollectionHandler ) : PaymentSheet.FlowController { private val paymentOptionActivityLauncher: ActivityResultLauncher @@ -120,18 +97,6 @@ internal class DefaultFlowController @Inject internal constructor( */ lateinit var flowControllerComponent: FlowControllerComponent - private val confirmationHandler = DefaultConfirmationHandler.Factory( - intentConfirmationInterceptor = intentConfirmationInterceptor, - paymentConfigurationProvider = lazyPaymentConfiguration, - statusBarColor = statusBarColor(), - savedStateHandle = viewModel.handle, - bacsMandateConfirmationLauncherFactory = bacsMandateConfirmationLauncherFactory, - stripePaymentLauncherAssistedFactory = paymentLauncherFactory, - googlePayPaymentMethodLauncherFactory = googlePayPaymentMethodLauncherFactory, - errorReporter = errorReporter, - logger = logger, - ).create(viewModelScope.plus(workContext)) - private val initializationMode: PaymentElementLoader.InitializationMode? get() = viewModel.previousConfigureRequest?.initializationMode @@ -296,7 +261,6 @@ internal class DefaultFlowController @Inject internal constructor( val args = PaymentOptionContract.Args( state = state.paymentSheetState.copy(paymentSelection = viewModel.paymentSelection), configuration = state.config, - statusBarColor = statusBarColor(), enableLogging = enableLogging, productUsage = productUsage, ) @@ -777,7 +741,7 @@ internal class DefaultFlowController @Inject internal constructor( ): PaymentSheet.FlowController { val flowControllerViewModel = ViewModelProvider( owner = viewModelStoreOwner, - factory = SavedStateViewModelFactory() + factory = FlowControllerViewModel.Factory(statusBarColor()) )[FlowControllerViewModel::class.java] val flowControllerStateComponent = flowControllerViewModel.flowControllerStateComponent @@ -786,7 +750,6 @@ internal class DefaultFlowController @Inject internal constructor( flowControllerStateComponent.flowControllerComponentBuilder .lifeCycleOwner(lifecycleOwner) .activityResultCaller(activityResultCaller) - .statusBarColor(statusBarColor) .paymentOptionCallback(paymentOptionCallback) .paymentResultCallback(paymentResultCallback) .initializedViaCompose(initializedViaCompose) diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/flowcontroller/FlowControllerComponent.kt b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/flowcontroller/FlowControllerComponent.kt index d4c4fb00224..6c3bb16db90 100644 --- a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/flowcontroller/FlowControllerComponent.kt +++ b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/flowcontroller/FlowControllerComponent.kt @@ -24,9 +24,6 @@ internal interface FlowControllerComponent { activityResultCaller: ActivityResultCaller, ): Builder - @BindsInstance - fun statusBarColor(statusBarColor: () -> Int?): Builder - @BindsInstance fun paymentOptionCallback(paymentOptionCallback: PaymentOptionCallback): Builder diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/flowcontroller/FlowControllerModule.kt b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/flowcontroller/FlowControllerModule.kt index 4d085c83a66..2a71fba6a1e 100644 --- a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/flowcontroller/FlowControllerModule.kt +++ b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/flowcontroller/FlowControllerModule.kt @@ -2,8 +2,11 @@ package com.stripe.android.paymentsheet.flowcontroller import android.app.Application import android.content.Context +import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.viewModelScope +import com.stripe.android.core.injection.IOContext import com.stripe.android.paymentelement.confirmation.ALLOWS_MANUAL_CONFIRMATION +import com.stripe.android.paymentelement.confirmation.ConfirmationHandler import com.stripe.android.payments.core.injection.PRODUCT_USAGE import com.stripe.android.paymentsheet.analytics.EventReporter import com.stripe.android.paymentsheet.injection.PaymentOptionsViewModelSubcomponent @@ -11,8 +14,10 @@ import com.stripe.android.uicore.image.StripeImageLoader import dagger.Module import dagger.Provides import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.plus import javax.inject.Named import javax.inject.Singleton +import kotlin.coroutines.CoroutineContext @Module( subcomponents = [ @@ -39,6 +44,26 @@ internal object FlowControllerModule { return viewModel.viewModelScope } + @Provides + @Singleton + fun providesSavedStateHandle( + viewModel: FlowControllerViewModel, + ): SavedStateHandle { + return viewModel.handle + } + + @Provides + @Singleton + fun providesConfirmationHandler( + confirmationHandlerFactory: ConfirmationHandler.Factory, + viewModel: FlowControllerViewModel, + @IOContext workContext: CoroutineContext, + ): ConfirmationHandler { + return confirmationHandlerFactory.create( + scope = viewModel.viewModelScope.plus(workContext) + ) + } + @Provides @Singleton fun provideStripeImageLoader(context: Context): StripeImageLoader { diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/flowcontroller/FlowControllerStateComponent.kt b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/flowcontroller/FlowControllerStateComponent.kt index 6560f118523..f2aed01d301 100644 --- a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/flowcontroller/FlowControllerStateComponent.kt +++ b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/flowcontroller/FlowControllerStateComponent.kt @@ -4,13 +4,16 @@ import android.app.Application import com.stripe.android.core.injection.CoreCommonModule import com.stripe.android.core.injection.CoroutineContextModule import com.stripe.android.googlepaylauncher.injection.GooglePayLauncherModule +import com.stripe.android.paymentelement.confirmation.ConfirmationHandler import com.stripe.android.paymentelement.confirmation.ConfirmationModule +import com.stripe.android.payments.core.injection.STATUS_BAR_COLOR import com.stripe.android.payments.core.injection.StripeRepositoryModule import com.stripe.android.paymentsheet.PaymentOptionsViewModel import com.stripe.android.paymentsheet.injection.PaymentSheetCommonModule import com.stripe.android.ui.core.forms.resources.injection.ResourceRepositoryModule import dagger.BindsInstance import dagger.Component +import javax.inject.Named import javax.inject.Singleton @Singleton @@ -28,11 +31,17 @@ import javax.inject.Singleton ) internal interface FlowControllerStateComponent { val flowControllerComponentBuilder: FlowControllerComponent.Builder + val confirmationHandler: ConfirmationHandler fun inject(paymentOptionsViewModel: PaymentOptionsViewModel.Factory) @Component.Builder interface Builder { + @BindsInstance + fun statusBarColor( + @Named(STATUS_BAR_COLOR) statusBarColor: Int? + ): Builder + @BindsInstance fun application(application: Application): Builder diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/flowcontroller/FlowControllerViewModel.kt b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/flowcontroller/FlowControllerViewModel.kt index 7d45fa991d8..dbf130dafd9 100644 --- a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/flowcontroller/FlowControllerViewModel.kt +++ b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/flowcontroller/FlowControllerViewModel.kt @@ -1,20 +1,28 @@ package com.stripe.android.paymentsheet.flowcontroller import android.app.Application +import androidx.annotation.ColorInt import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.createSavedStateHandle +import androidx.lifecycle.viewmodel.CreationExtras import com.stripe.android.analytics.SessionSavedStateHandler +import com.stripe.android.core.utils.requireApplication import com.stripe.android.paymentsheet.model.PaymentSelection internal class FlowControllerViewModel( application: Application, val handle: SavedStateHandle, + @ColorInt statusBarColor: Int?, ) : AndroidViewModel(application) { val flowControllerStateComponent: FlowControllerStateComponent = DaggerFlowControllerStateComponent .builder() .application(application) + .statusBarColor(statusBarColor) .flowControllerViewModel(this) .build() @@ -37,6 +45,19 @@ internal class FlowControllerViewModel( restartSession() } + class Factory( + @ColorInt private val statusBarColor: Int? + ) : ViewModelProvider.Factory { + @Suppress("UNCHECKED_CAST") + override fun create(modelClass: Class, extras: CreationExtras): T { + return FlowControllerViewModel( + application = extras.requireApplication(), + handle = extras.createSavedStateHandle(), + statusBarColor = statusBarColor, + ) as T + } + } + private companion object { private const val STATE_KEY = "state" } diff --git a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/PaymentOptionsViewModelTest.kt b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/PaymentOptionsViewModelTest.kt index fb7b18b2bb1..e11ca38e386 100644 --- a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/PaymentOptionsViewModelTest.kt +++ b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/PaymentOptionsViewModelTest.kt @@ -889,7 +889,6 @@ internal class PaymentOptionsViewModelTest { ), ), configuration = PaymentSheetFixtures.CONFIG_CUSTOMER_WITH_GOOGLEPAY, - statusBarColor = PaymentSheetFixtures.STATUS_BAR_COLOR, enableLogging = false, productUsage = mock() ) 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 1f3830fa51e..3b187822d06 100644 --- a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/PaymentSheetFixtures.kt +++ b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/PaymentSheetFixtures.kt @@ -139,7 +139,6 @@ internal object PaymentSheetFixtures { paymentMethodMetadata = PaymentMethodMetadataFactory.create(), ), configuration = CONFIG_GOOGLEPAY, - statusBarColor = STATUS_BAR_COLOR, enableLogging = false, productUsage = mock() ) 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 b3075fc77cf..567ba8292a5 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 @@ -38,6 +38,7 @@ import com.stripe.android.model.PaymentMethodFixtures import com.stripe.android.model.PaymentMethodOptionsParams import com.stripe.android.model.StripeIntent import com.stripe.android.model.wallets.Wallet +import com.stripe.android.paymentelement.confirmation.DefaultConfirmationHandler import com.stripe.android.paymentelement.confirmation.intent.DeferredIntentConfirmationType import com.stripe.android.paymentelement.confirmation.intent.IntentConfirmationInterceptor import com.stripe.android.paymentelement.confirmation.intent.InvalidDeferredIntentUsageException @@ -464,7 +465,6 @@ internal class DefaultFlowControllerTest { paymentMethodMetadata = PaymentMethodMetadataFactory.create(allowsDelayedPaymentMethods = false), ), configuration = PaymentSheet.Configuration("com.stripe.android.paymentsheet.test"), - statusBarColor = STATUS_BAR_COLOR, enableLogging = ENABLE_LOGGING, productUsage = PRODUCT_USAGE ) @@ -2384,7 +2384,6 @@ internal class DefaultFlowControllerTest { viewModelScope = testScope, lifecycleOwner = lifecycleOwner, activityResultCaller = activityResultCaller, - statusBarColor = { STATUS_BAR_COLOR }, paymentOptionFactory = PaymentOptionFactory( iconLoader = PaymentSelection.IconLoader( resources = context.resources, @@ -2397,15 +2396,9 @@ internal class DefaultFlowControllerTest { context = context, eventReporter = eventReporter, viewModel = viewModel, - paymentLauncherFactory = paymentLauncherAssistedFactory, - lazyPaymentConfiguration = { - PaymentConfiguration.getInstance(context) - }, enableLogging = ENABLE_LOGGING, productUsage = PRODUCT_USAGE, - googlePayPaymentMethodLauncherFactory = googlePayPaymentMethodLauncherFactory, prefsRepositoryFactory = { prefsRepository }, - bacsMandateConfirmationLauncherFactory = bacsMandateConfirmationLauncherFactory, linkLauncher = linkPaymentLauncher, configurationHandler = FlowControllerConfigurationHandler( paymentElementLoader = paymentElementLoader, @@ -2414,12 +2407,22 @@ internal class DefaultFlowControllerTest { viewModel = viewModel, paymentSelectionUpdater = { _, _, newState, _ -> newState.paymentSelection }, ), - intentConfirmationInterceptor = fakeIntentConfirmationInterceptor, errorReporter = errorReporter, cvcRecollectionLauncherFactory = cvcRecollectionLauncherFactory, initializedViaCompose = false, - workContext = testScope.coroutineContext, - logger = FakeUserFacingLogger(), + confirmationHandler = DefaultConfirmationHandler.Factory( + bacsMandateConfirmationLauncherFactory = bacsMandateConfirmationLauncherFactory, + googlePayPaymentMethodLauncherFactory = googlePayPaymentMethodLauncherFactory, + intentConfirmationInterceptor = fakeIntentConfirmationInterceptor, + stripePaymentLauncherAssistedFactory = paymentLauncherAssistedFactory, + paymentConfigurationProvider = { + PaymentConfiguration.getInstance(context) + }, + logger = FakeUserFacingLogger(), + errorReporter = errorReporter, + savedStateHandle = viewModel.handle, + statusBarColor = STATUS_BAR_COLOR, + ).create(testScope), cvcRecollectionHandler = CvcRecollectionHandlerImpl() ) @@ -2427,6 +2430,7 @@ internal class DefaultFlowControllerTest { return FlowControllerViewModel( application = ApplicationProvider.getApplicationContext(), handle = SavedStateHandle(), + statusBarColor = STATUS_BAR_COLOR, ) } diff --git a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/flowcontroller/FlowControllerConfigurationHandlerTest.kt b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/flowcontroller/FlowControllerConfigurationHandlerTest.kt index d167c7bca26..a860e37e203 100644 --- a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/flowcontroller/FlowControllerConfigurationHandlerTest.kt +++ b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/flowcontroller/FlowControllerConfigurationHandlerTest.kt @@ -67,7 +67,8 @@ class FlowControllerConfigurationHandlerTest { viewModel = FlowControllerViewModel( application = ApplicationProvider.getApplicationContext(), - handle = SavedStateHandle() + handle = SavedStateHandle(), + statusBarColor = null, ) }