Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tolu/link confirm payment #9802

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ internal class LinkActivity : ComponentActivity() {
}

val vm = viewModel ?: return
vm.registerFromActivity(
activityResultCaller = this,
lifecycleOwner = this,
)

setContent {
var bottomSheetContent by remember { mutableStateOf<BottomSheetContent?>(null) }
val sheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.stripe.android.link

import android.app.Application
import androidx.activity.result.ActivityResultCaller
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.SavedStateHandle
Expand All @@ -23,6 +24,7 @@ import com.stripe.android.paymentelement.confirmation.ConfirmationHandler
import com.stripe.android.paymentsheet.R
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import javax.inject.Inject
Expand All @@ -49,6 +51,27 @@ internal class LinkActivityViewModel @Inject constructor(
var navController: NavHostController? = null
var dismissWithResult: ((LinkActivityResult) -> Unit)? = null

init {
viewModelScope.launch {
listenToConfirmationState()
}
}

private suspend fun listenToConfirmationState() {
// confirmationHandler.state
// .filterIsInstance<ConfirmationHandler.State.Complete>()
// .collect {
// dismissWithResult?.invoke(LinkActivityResult.Completed)
// }
}

fun registerFromActivity(
activityResultCaller: ActivityResultCaller,
lifecycleOwner: LifecycleOwner,
) {
confirmationHandler.register(activityResultCaller, lifecycleOwner)
}

fun handleViewAction(action: LinkAction) {
when (action) {
LinkAction.BackPressed -> handleBackPressed()
Expand Down Expand Up @@ -117,6 +140,7 @@ internal class LinkActivityViewModel @Inject constructor(
.stripeAccountIdProvider { args.stripeAccountId }
.savedStateHandle(handle)
.context(app)
.savedStateHandle(handle)
.build()
.viewModel
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.stripe.android.link.injection

import com.stripe.android.link.LinkActivityViewModel
import com.stripe.android.link.account.LinkAccountManager
import com.stripe.android.paymentelement.confirmation.DefaultConfirmationHandler
import dagger.Module
import dagger.Provides

@Module
internal object LinkViewModelModule {
@Provides
@NativeLinkScope
fun provideLinkActivityViewModel(
component: NativeLinkComponent,
defaultConfirmationHandlerFactory: DefaultConfirmationHandler.Factory,
linkAccountManager: LinkAccountManager
): LinkActivityViewModel {
return LinkActivityViewModel(
component,
defaultConfirmationHandlerFactory,
linkAccountManager
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ internal annotation class NativeLinkScope
modules = [
NativeLinkModule::class,
DefaultConfirmationModule::class,
// ViewModelModule::class,
LinkViewModelModule::class,
DefaultConfirmationModule::class,
]
)
internal interface NativeLinkComponent {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,8 @@ internal interface NativeLinkModule {
factory: DefaultLinkConfirmationHandler.Factory
): LinkConfirmationHandler.Factory = factory

@Provides
@NativeLinkScope
fun provideEventReporterMode(): EventReporter.Mode = EventReporter.Mode.Custom
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import com.stripe.android.link.theme.HorizontalPadding
import com.stripe.android.link.theme.linkColors
import com.stripe.android.link.theme.linkShapes
import com.stripe.android.link.ui.BottomSheetContent
import com.stripe.android.link.ui.ErrorText
import com.stripe.android.link.ui.PrimaryButton
import com.stripe.android.link.ui.SecondaryButton
import com.stripe.android.model.ConsumerPaymentDetails
Expand Down Expand Up @@ -91,17 +92,11 @@ internal fun WalletBody(
hideBottomSheetContent: () -> Unit
) {
if (state.paymentDetailsList.isEmpty()) {
Box(
modifier = Modifier
.fillMaxSize()
.testTag(WALLET_LOADER_TAG),
contentAlignment = Alignment.Center
) {
CircularProgressIndicator()
}
Loader()
return
}

val context = LocalContext.current
val focusManager = LocalFocusManager.current

LaunchedEffect(state.isProcessing) {
Expand Down Expand Up @@ -133,6 +128,15 @@ internal fun WalletBody(
BankAccountTerms()
}

AnimatedVisibility(visible = state.errorMessage != null) {
ErrorText(
text = state.errorMessage?.resolve(context).orEmpty(),
modifier = Modifier
.fillMaxWidth()
.padding(top = 16.dp)
)
}

Spacer(modifier = Modifier.height(16.dp))

PrimaryButton(
Expand All @@ -154,6 +158,18 @@ internal fun WalletBody(
}
}

@Composable
private fun Loader() {
Box(
modifier = Modifier
.fillMaxSize()
.testTag(WALLET_LOADER_TAG),
contentAlignment = Alignment.Center
) {
CircularProgressIndicator()
}
}

@Composable
private fun PaymentMethodSection(
state: WalletUiState,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory
import com.stripe.android.common.exception.stripeErrorMessage
import com.stripe.android.core.Logger
import com.stripe.android.core.strings.resolvableString
import com.stripe.android.link.LinkActivityResult
import com.stripe.android.link.LinkConfiguration
import com.stripe.android.link.LinkScreen
Expand All @@ -21,6 +21,10 @@ import com.stripe.android.model.ConsumerPaymentDetails
import com.stripe.android.model.ConsumerPaymentDetailsUpdateParams
import com.stripe.android.model.PaymentMethod
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.state.PaymentElementLoader
import com.stripe.android.ui.core.FieldValuesToParamsMapConverter
import com.stripe.android.ui.core.elements.CardDetailsUtil.createExpiryDateFormFieldValues
import com.stripe.android.ui.core.elements.CvcController
Expand All @@ -34,11 +38,7 @@ import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import javax.inject.Inject
import kotlin.Result
import kotlin.String
import kotlin.Throwable
import kotlin.Unit
import kotlin.fold
import kotlin.takeIf
import com.stripe.android.link.confirmation.Result as LinkConfirmationResult

internal class WalletViewModel @Inject constructor(
Expand All @@ -48,6 +48,7 @@ internal class WalletViewModel @Inject constructor(
private val linkConfirmationHandler: LinkConfirmationHandler,
private val logger: Logger,
private val navigate: (route: LinkScreen) -> Unit,
private val confirmationHandler: ConfirmationHandler,
private val navigateAndClearStack: (route: LinkScreen) -> Unit,
private val dismissWithResult: (LinkActivityResult) -> Unit
) : ViewModel() {
Expand All @@ -59,7 +60,8 @@ internal class WalletViewModel @Inject constructor(
selectedItem = null,
isProcessing = false,
hasCompleted = false,
primaryButtonLabel = completePaymentButtonLabel(configuration.stripeIntent)
primaryButtonLabel = completePaymentButtonLabel(configuration.stripeIntent),
errorMessage = null
)
)

Expand Down Expand Up @@ -151,55 +153,65 @@ internal class WalletViewModel @Inject constructor(
val card = selectedPaymentDetails as? ConsumerPaymentDetails.Card
val isExpired = card != null && card.isExpired

if (isExpired) {
performPaymentDetailsUpdate(selectedPaymentDetails).fold(
onSuccess = { result ->
val updatedPaymentDetails = result.paymentDetails.single {
it.id == selectedPaymentDetails.id
}
performPaymentConfirmation(updatedPaymentDetails)
},
onFailure = { error ->
performPaymentConfirmationWithCvc(
selectedPaymentDetails = selectedPaymentDetails,
cvc = cvcController.formFieldValue.value.takeIf { it.isComplete }?.value
)
}

private suspend fun performPaymentConfirmationWithCvc(
selectedPaymentDetails: ConsumerPaymentDetails.PaymentDetails,
cvc: String?
) {
viewModelScope.launch {
confirmationHandler.start(
arguments = ConfirmationHandler.Args(
intent = configuration.stripeIntent,
confirmationOption = PaymentMethodConfirmationOption.New(
createParams = createPaymentMethodCreateParams(
selectedPaymentDetails = selectedPaymentDetails,
linkAccount = linkAccount
),
optionsParams = null,
shouldSave = false
),
appearance = PaymentSheet.Appearance(),
initializationMode = PaymentElementLoader.InitializationMode.PaymentIntent(
clientSecret = configuration.stripeIntent.clientSecret ?: ""
),
shippingDetails = null
)
)
val result = confirmationHandler.awaitResult()
when (result) {
is ConfirmationHandler.Result.Succeeded -> {
dismissWithResult(LinkActivityResult.Completed)
}
is ConfirmationHandler.Result.Canceled -> {
dismissWithResult(LinkActivityResult.Canceled(LinkActivityResult.Canceled.Reason.BackPressed))
}
is ConfirmationHandler.Result.Failed -> {
_uiState.update {
it.copy(
alertMessage = error.stripeErrorMessage(),
errorMessage = result.message,
isProcessing = false
)
}
}
)
} else {
// Confirm payment with LinkConfirmationHandler
performPaymentConfirmationWithCvc(
selectedPaymentDetails = selectedPaymentDetails,
cvc = cvcController.formFieldValue.value.takeIf { it.isComplete }?.value
)
null -> Unit
}
}
}

private suspend fun performPaymentConfirmationWithCvc(
private fun createPaymentMethodCreateParams(
selectedPaymentDetails: ConsumerPaymentDetails.PaymentDetails,
cvc: String?
) {
val result = linkConfirmationHandler.confirm(
paymentDetails = selectedPaymentDetails,
linkAccount = linkAccount,
cvc = cvc
linkAccount: LinkAccount,
): PaymentMethodCreateParams {
return PaymentMethodCreateParams.createLink(
paymentDetailsId = selectedPaymentDetails.id,
consumerSessionClientSecret = linkAccount.clientSecret,
extraParams = emptyMap(),
)
when (result) {
LinkConfirmationResult.Canceled -> Unit
is LinkConfirmationResult.Failed -> {
_uiState.update {
it.copy(
errorMessage = result.message,
isProcessing = false
)
}
}
LinkConfirmationResult.Succeeded -> {
dismissWithResult(LinkActivityResult.Completed)
}
}
}

private suspend fun performPaymentDetailsUpdate(
Expand Down Expand Up @@ -248,6 +260,7 @@ internal class WalletViewModel @Inject constructor(
logger = parentComponent.logger,
linkAccount = linkAccount,
navigate = navigate,
confirmationHandler = parentComponent.viewModel.confirmationHandler,
navigateAndClearStack = navigateAndClearStack,
dismissWithResult = dismissWithResult
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import com.stripe.android.model.PaymentMethod
import com.stripe.android.model.PaymentMethodCreateParams
import com.stripe.android.paymentsheet.PaymentSheet
import com.stripe.android.paymentsheet.paymentdatacollection.FormArguments
import com.stripe.android.paymentsheet.addresselement.AddressDetails
import com.stripe.android.ui.core.Amount
import com.stripe.android.ui.core.cbc.CardBrandChoiceEligibility
import org.mockito.kotlin.mock
Expand Down
Loading
Loading