Skip to content

Commit

Permalink
Inject ConfirmationHandler into FlowController (#9651)
Browse files Browse the repository at this point in the history
  • Loading branch information
samer-stripe authored Dec 2, 2024
1 parent 8c2042a commit 865db79
Show file tree
Hide file tree
Showing 10 changed files with 74 additions and 58 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<String>
) : ActivityStarter.Args {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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,
Expand All @@ -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<PaymentConfiguration>,
private val confirmationHandler: ConfirmationHandler,
@Named(ENABLE_LOGGING) private val enableLogging: Boolean,
@Named(PRODUCT_USAGE) private val productUsage: Set<String>,
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<PaymentOptionContract.Args>
Expand All @@ -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

Expand Down Expand Up @@ -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,
)
Expand Down Expand Up @@ -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
Expand All @@ -786,7 +750,6 @@ internal class DefaultFlowController @Inject internal constructor(
flowControllerStateComponent.flowControllerComponentBuilder
.lifeCycleOwner(lifecycleOwner)
.activityResultCaller(activityResultCaller)
.statusBarColor(statusBarColor)
.paymentOptionCallback(paymentOptionCallback)
.paymentResultCallback(paymentResultCallback)
.initializedViaCompose(initializedViaCompose)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,6 @@ internal interface FlowControllerComponent {
activityResultCaller: ActivityResultCaller,
): Builder

@BindsInstance
fun statusBarColor(statusBarColor: () -> Int?): Builder

@BindsInstance
fun paymentOptionCallback(paymentOptionCallback: PaymentOptionCallback): Builder

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,22 @@ 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
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 = [
Expand All @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down
Original file line number Diff line number Diff line change
@@ -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()

Expand All @@ -37,6 +45,19 @@ internal class FlowControllerViewModel(
restartSession()
}

class Factory(
@ColorInt private val statusBarColor: Int?
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T {
return FlowControllerViewModel(
application = extras.requireApplication(),
handle = extras.createSavedStateHandle(),
statusBarColor = statusBarColor,
) as T
}
}

private companion object {
private const val STATE_KEY = "state"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -889,7 +889,6 @@ internal class PaymentOptionsViewModelTest {
),
),
configuration = PaymentSheetFixtures.CONFIG_CUSTOMER_WITH_GOOGLEPAY,
statusBarColor = PaymentSheetFixtures.STATUS_BAR_COLOR,
enableLogging = false,
productUsage = mock()
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,6 @@ internal object PaymentSheetFixtures {
paymentMethodMetadata = PaymentMethodMetadataFactory.create(),
),
configuration = CONFIG_GOOGLEPAY,
statusBarColor = STATUS_BAR_COLOR,
enableLogging = false,
productUsage = mock()
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
)
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -2414,19 +2407,30 @@ 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()
)

private fun createViewModel(): FlowControllerViewModel {
return FlowControllerViewModel(
application = ApplicationProvider.getApplicationContext(),
handle = SavedStateHandle(),
statusBarColor = STATUS_BAR_COLOR,
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ class FlowControllerConfigurationHandlerTest {

viewModel = FlowControllerViewModel(
application = ApplicationProvider.getApplicationContext(),
handle = SavedStateHandle()
handle = SavedStateHandle(),
statusBarColor = null,
)
}

Expand Down

0 comments on commit 865db79

Please sign in to comment.