Skip to content

Commit

Permalink
Link Confirmation Handler (#9865)
Browse files Browse the repository at this point in the history
  • Loading branch information
toluo-stripe authored Jan 9, 2025
1 parent fbc3daf commit 1b50c6d
Show file tree
Hide file tree
Showing 6 changed files with 400 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ internal class LinkActivityViewModel @Inject constructor(
.configuration(args.configuration)
.publishableKeyProvider { args.publishableKey }
.stripeAccountIdProvider { args.stripeAccountId }
.savedStateHandle(handle)
.context(app)
.build()
.viewModel
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
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.PaymentIntent
import com.stripe.android.model.PaymentMethodCreateParams
import com.stripe.android.model.SetupIntent
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 {
return runCatching {
val args = confirmationArgs(paymentDetails, linkAccount)
confirmationHandler.start(args)
val result = confirmationHandler.awaitResult()
transformResult(result)
}.getOrElse { error ->
logger.error(
msg = "DefaultLinkConfirmationHandler: Failed to confirm payment",
t = error
)
Result.Failed(R.string.stripe_something_went_wrong.resolvableString)
}
}

private fun transformResult(result: ConfirmationHandler.Result?): Result {
return when (result) {
is ConfirmationHandler.Result.Canceled -> Result.Canceled
is ConfirmationHandler.Result.Failed -> {
logger.error(
msg = "DefaultLinkConfirmationHandler: Failed to confirm payment",
t = result.cause
)
Result.Failed(result.message)
}
is ConfirmationHandler.Result.Succeeded -> Result.Succeeded
null -> {
logger.error("DefaultLinkConfirmationHandler: 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 = initializationMode(),
shippingDetails = configuration.shippingDetails
)
}

private fun initializationMode(): PaymentElementLoader.InitializationMode {
val clientSecret = configuration.stripeIntent.clientSecret ?: throw NO_CLIENT_SECRET_FOUND
return when (configuration.stripeIntent) {
is PaymentIntent -> {
PaymentElementLoader.InitializationMode.PaymentIntent(clientSecret)
}
is SetupIntent -> {
PaymentElementLoader.InitializationMode.SetupIntent(clientSecret)
}
}
}

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
)
}
}

companion object {
val NO_CLIENT_SECRET_FOUND = IllegalStateException("No client secret found.")
}
}
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
}
}

internal 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
@@ -1,13 +1,17 @@
package com.stripe.android.link.injection

import android.content.Context
import androidx.lifecycle.SavedStateHandle
import com.stripe.android.core.Logger
import com.stripe.android.core.injection.PUBLISHABLE_KEY
import com.stripe.android.core.injection.STRIPE_ACCOUNT_ID
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
import dagger.Component
import javax.inject.Named
Expand All @@ -21,13 +25,15 @@ internal annotation class NativeLinkScope
@Component(
modules = [
NativeLinkModule::class,
DefaultConfirmationModule::class,
]
)
internal interface NativeLinkComponent {
val linkAccountManager: LinkAccountManager
val configuration: LinkConfiguration
val linkEventsReporter: LinkEventsReporter
val logger: Logger
val linkConfirmationHandlerFactory: LinkConfirmationHandler.Factory
val viewModel: LinkActivityViewModel

@Component.Builder
Expand All @@ -44,6 +50,12 @@ internal interface NativeLinkComponent {
@BindsInstance
fun context(context: Context): Builder

@BindsInstance
fun savedStateHandle(savedStateHandle: SavedStateHandle): Builder

@BindsInstance
fun statusBarColor(@Named(STATUS_BAR_COLOR) statusBarColor: Int?): Builder

fun build(): NativeLinkComponent
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.stripe.android.link.injection
import android.content.Context
import androidx.core.os.LocaleListCompat
import com.stripe.android.BuildConfig
import com.stripe.android.PaymentConfiguration
import com.stripe.android.Stripe
import com.stripe.android.core.Logger
import com.stripe.android.core.injection.ENABLE_LOGGING
Expand All @@ -21,10 +22,13 @@ 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
import com.stripe.android.networking.StripeRepository
import com.stripe.android.paymentelement.confirmation.ALLOWS_MANUAL_CONFIRMATION
import com.stripe.android.payments.core.analytics.ErrorReporter
import com.stripe.android.payments.core.analytics.RealErrorReporter
import com.stripe.android.payments.core.injection.PRODUCT_USAGE
Expand Down Expand Up @@ -59,6 +63,7 @@ internal interface NativeLinkModule {
@NativeLinkScope
fun stripeRepository(stripeRepository: StripeApiRepository): StripeRepository

@SuppressWarnings("TooManyFunctions")
companion object {
@Provides
@NativeLinkScope
Expand Down Expand Up @@ -116,13 +121,30 @@ internal interface NativeLinkModule {

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

@Provides
@Named(ENABLE_LOGGING)
@NativeLinkScope
fun providesEnableLogging(): Boolean = BuildConfig.DEBUG

@Provides
@NativeLinkScope
fun providePaymentConfiguration(appContext: Context): PaymentConfiguration {
return PaymentConfiguration.getInstance(appContext)
}

@Provides
@NativeLinkScope
@Named(ALLOWS_MANUAL_CONFIRMATION)
fun provideAllowsManualConfirmation() = true

@Provides
@NativeLinkScope
fun provideLinkConfirmationHandlerFactory(
factory: DefaultLinkConfirmationHandler.Factory
): LinkConfirmationHandler.Factory = factory
}
}
Loading

0 comments on commit 1b50c6d

Please sign in to comment.