Skip to content

Commit

Permalink
Abstract confirmation
Browse files Browse the repository at this point in the history
  • Loading branch information
toluo-stripe committed Jan 4, 2025
1 parent 169b022 commit f243637
Show file tree
Hide file tree
Showing 9 changed files with 302 additions and 51 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package com.stripe.android.link.confirmation

import com.stripe.android.core.Logger
import com.stripe.android.core.strings.resolvableString
import com.stripe.android.link.LinkConfiguration
import com.stripe.android.link.model.LinkAccount
import com.stripe.android.model.ConsumerPaymentDetails
import com.stripe.android.model.PaymentMethodCreateParams
import com.stripe.android.paymentelement.confirmation.ConfirmationHandler
import com.stripe.android.paymentelement.confirmation.PaymentMethodConfirmationOption
import com.stripe.android.paymentsheet.PaymentSheet
import com.stripe.android.paymentsheet.R
import com.stripe.android.paymentsheet.state.PaymentElementLoader
import javax.inject.Inject

internal class DefaultLinkConfirmationHandler @Inject constructor(
private val configuration: LinkConfiguration,
private val logger: Logger,
private val confirmationHandler: ConfirmationHandler
) : LinkConfirmationHandler {
override suspend fun confirm(
paymentDetails: ConsumerPaymentDetails.PaymentDetails,
linkAccount: LinkAccount
): Result {
val args = confirmationArgs(paymentDetails, linkAccount)
confirmationHandler.start(args)
val result = confirmationHandler.awaitResult()
return transformResult(result)
}

private fun transformResult(result: ConfirmationHandler.Result?): Result {
return when (result) {
is ConfirmationHandler.Result.Canceled -> Result.Canceled
is ConfirmationHandler.Result.Failed -> {
logger.error(
msg = "Failed to confirm payment",
t = result.cause
)
Result.Failed(result.message)
}
is ConfirmationHandler.Result.Succeeded -> Result.Succeeded
null -> {
logger.error("Payment confirmation returned null")
Result.Failed(R.string.stripe_something_went_wrong.resolvableString)
}
}
}

private fun confirmationArgs(
paymentDetails: ConsumerPaymentDetails.PaymentDetails,
linkAccount: LinkAccount
): ConfirmationHandler.Args {
return ConfirmationHandler.Args(
intent = configuration.stripeIntent,
confirmationOption = PaymentMethodConfirmationOption.New(
createParams = createPaymentMethodCreateParams(
selectedPaymentDetails = paymentDetails,
linkAccount = linkAccount
),
optionsParams = null,
shouldSave = false
),
appearance = PaymentSheet.Appearance(),
initializationMode = PaymentElementLoader.InitializationMode.PaymentIntent(
clientSecret = configuration.stripeIntent.clientSecret ?: ""
),
shippingDetails = null
)
}

private fun createPaymentMethodCreateParams(
selectedPaymentDetails: ConsumerPaymentDetails.PaymentDetails,
linkAccount: LinkAccount,
): PaymentMethodCreateParams {
return PaymentMethodCreateParams.createLink(
paymentDetailsId = selectedPaymentDetails.id,
consumerSessionClientSecret = linkAccount.clientSecret,
extraParams = emptyMap(),
)
}

class Factory @Inject constructor(
private val configuration: LinkConfiguration,
private val logger: Logger,
) : LinkConfirmationHandler.Factory {
override fun create(confirmationHandler: ConfirmationHandler): LinkConfirmationHandler {
return DefaultLinkConfirmationHandler(
confirmationHandler = confirmationHandler,
logger = logger,
configuration = configuration
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.stripe.android.link.confirmation

import com.stripe.android.core.strings.ResolvableString
import com.stripe.android.link.model.LinkAccount
import com.stripe.android.model.ConsumerPaymentDetails
import com.stripe.android.paymentelement.confirmation.ConfirmationHandler

internal interface LinkConfirmationHandler {
suspend fun confirm(
paymentDetails: ConsumerPaymentDetails.PaymentDetails,
linkAccount: LinkAccount
): Result

fun interface Factory {
fun create(confirmationHandler: ConfirmationHandler): LinkConfirmationHandler
}
}

sealed interface Result {
data object Succeeded : Result
data object Canceled : Result
data class Failed(val message: ResolvableString) : Result
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import com.stripe.android.link.LinkActivityViewModel
import com.stripe.android.link.LinkConfiguration
import com.stripe.android.link.account.LinkAccountManager
import com.stripe.android.link.analytics.LinkEventsReporter
import com.stripe.android.link.confirmation.LinkConfirmationHandler
import com.stripe.android.paymentelement.confirmation.injection.DefaultConfirmationModule
import com.stripe.android.payments.core.injection.STATUS_BAR_COLOR
import dagger.BindsInstance
Expand All @@ -33,6 +34,7 @@ internal interface NativeLinkComponent {
val configuration: LinkConfiguration
val linkEventsReporter: LinkEventsReporter
val logger: Logger
val linkConfirmationHandlerFactory: LinkConfirmationHandler.Factory
val viewModel: LinkActivityViewModel

@Component.Builder
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import com.stripe.android.link.account.DefaultLinkAccountManager
import com.stripe.android.link.account.LinkAccountManager
import com.stripe.android.link.analytics.DefaultLinkEventsReporter
import com.stripe.android.link.analytics.LinkEventsReporter
import com.stripe.android.link.confirmation.DefaultLinkConfirmationHandler
import com.stripe.android.link.confirmation.LinkConfirmationHandler
import com.stripe.android.link.repositories.LinkApiRepository
import com.stripe.android.link.repositories.LinkRepository
import com.stripe.android.networking.StripeApiRepository
Expand Down Expand Up @@ -119,7 +121,7 @@ internal interface NativeLinkModule {

@Provides
@NativeLinkScope
internal fun providesAnalyticsRequestExecutor(
fun providesAnalyticsRequestExecutor(
executor: DefaultAnalyticsRequestExecutor
): AnalyticsRequestExecutor = executor

Expand All @@ -138,5 +140,11 @@ internal interface NativeLinkModule {
@NativeLinkScope
@Named(ALLOWS_MANUAL_CONFIRMATION)
fun provideAllowsManualConfirmation() = true

@Provides
@NativeLinkScope
fun provideLinkConfirmationHandlerFactory(
factory: DefaultLinkConfirmationHandler.Factory
): LinkConfirmationHandler.Factory = factory
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,15 @@ import com.stripe.android.link.LinkActivityResult
import com.stripe.android.link.LinkConfiguration
import com.stripe.android.link.LinkScreen
import com.stripe.android.link.account.LinkAccountManager
import com.stripe.android.link.confirmation.LinkConfirmationHandler
import com.stripe.android.link.confirmation.Result
import com.stripe.android.link.injection.NativeLinkComponent
import com.stripe.android.link.model.LinkAccount
import com.stripe.android.link.model.supportedPaymentMethodTypes
import com.stripe.android.model.ConsumerPaymentDetails
import com.stripe.android.model.PaymentIntent
import com.stripe.android.model.PaymentMethodCreateParams
import com.stripe.android.model.SetupIntent
import com.stripe.android.model.StripeIntent
import com.stripe.android.paymentelement.confirmation.ConfirmationHandler
import com.stripe.android.paymentelement.confirmation.PaymentMethodConfirmationOption
import com.stripe.android.paymentsheet.PaymentSheet
import com.stripe.android.paymentsheet.state.PaymentElementLoader
import com.stripe.android.ui.core.Amount
import com.stripe.android.ui.core.R
import kotlinx.coroutines.flow.MutableStateFlow
Expand All @@ -36,7 +33,7 @@ internal class WalletViewModel @Inject constructor(
private val linkAccount: LinkAccount,
private val linkAccountManager: LinkAccountManager,
private val logger: Logger,
private val confirmationHandler: ConfirmationHandler,
private val linkConfirmationHandler: LinkConfirmationHandler,
private val navigateAndClearStack: (route: LinkScreen) -> Unit,
private val dismissWithResult: (LinkActivityResult) -> Unit
) : ViewModel() {
Expand Down Expand Up @@ -87,14 +84,6 @@ internal class WalletViewModel @Inject constructor(
dismissWithResult(LinkActivityResult.Failed(fatalError))
}

private fun onError(error: Throwable) {
_uiState.update {
it.copy(
errorMessage = error.message?.resolvableString
)
}
}

fun onItemSelected(item: ConsumerPaymentDetails.PaymentDetails) {
if (item == uiState.value.selectedItem) return

Expand All @@ -106,46 +95,26 @@ internal class WalletViewModel @Inject constructor(
fun onPrimaryButtonClicked() {
val selectedItem = uiState.value.selectedItem ?: return
viewModelScope.launch {
confirmationHandler.start(
arguments = ConfirmationHandler.Args(
intent = configuration.stripeIntent,
confirmationOption = PaymentMethodConfirmationOption.New(
createParams = createPaymentMethodCreateParams(
selectedPaymentDetails = selectedItem,
linkAccount = linkAccount
),
optionsParams = null,
shouldSave = false
),
appearance = PaymentSheet.Appearance(),
initializationMode = PaymentElementLoader.InitializationMode.PaymentIntent(
clientSecret = configuration.stripeIntent.clientSecret ?: ""
),
shippingDetails = null
)
val result = linkConfirmationHandler.confirm(
paymentDetails = selectedItem,
linkAccount = linkAccount
)
when (val result = confirmationHandler.awaitResult()) {
is ConfirmationHandler.Result.Canceled -> {
when (result) {
Result.Canceled -> {
dismissWithResult(LinkActivityResult.Canceled(LinkActivityResult.Canceled.Reason.BackPressed))
}
is ConfirmationHandler.Result.Failed -> onError(result.cause)
is ConfirmationHandler.Result.Succeeded -> Unit
null -> Unit
is Result.Failed -> {
_uiState.update {
it.copy(
errorMessage = result.message
)
}
}
Result.Succeeded -> Unit
}
}
}

private fun createPaymentMethodCreateParams(
selectedPaymentDetails: ConsumerPaymentDetails.PaymentDetails,
linkAccount: LinkAccount,
): PaymentMethodCreateParams {
return PaymentMethodCreateParams.createLink(
paymentDetailsId = selectedPaymentDetails.id,
consumerSessionClientSecret = linkAccount.clientSecret,
extraParams = emptyMap(),
)
}

fun onPayAnotherWayClicked() {
dismissWithResult(LinkActivityResult.Canceled(LinkActivityResult.Canceled.Reason.PayAnotherWay))
}
Expand Down Expand Up @@ -176,7 +145,9 @@ internal class WalletViewModel @Inject constructor(
linkAccountManager = parentComponent.linkAccountManager,
logger = parentComponent.logger,
linkAccount = linkAccount,
confirmationHandler = parentComponent.viewModel.confirmationHandler,
linkConfirmationHandler = parentComponent.linkConfirmationHandlerFactory.create(
confirmationHandler = parentComponent.viewModel.confirmationHandler
),
navigateAndClearStack = navigateAndClearStack,
dismissWithResult = dismissWithResult
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,12 +98,12 @@ class LinkActivityResultTest {
@Test
fun `complete with result from native link`() {
val bundle = bundleOf(
LinkActivityContract.EXTRA_RESULT to LinkActivityResult.Completed(PaymentMethodFixtures.CARD_PAYMENT_METHOD)
LinkActivityContract.EXTRA_RESULT to LinkActivityResult.Completed
)
val intent = Intent()
intent.putExtras(bundle)
val result = createLinkActivityResult(LinkActivity.RESULT_COMPLETE, intent)
assertThat(result).isEqualTo(LinkActivityResult.Completed(PaymentMethodFixtures.CARD_PAYMENT_METHOD))
assertThat(result).isEqualTo(LinkActivityResult.Completed)
}

@Test
Expand Down
Loading

0 comments on commit f243637

Please sign in to comment.