Skip to content

Commit

Permalink
Payment Confirmation on Card creation screen (#9918)
Browse files Browse the repository at this point in the history
  • Loading branch information
toluo-stripe authored Jan 15, 2025
1 parent 81cec80 commit ac56735
Show file tree
Hide file tree
Showing 6 changed files with 288 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ package com.stripe.android.link.ui.paymentmenthod

import com.stripe.android.core.strings.ResolvableString
import com.stripe.android.link.ui.PrimaryButtonState
import com.stripe.android.paymentsheet.model.PaymentSelection
import com.stripe.android.model.PaymentMethodCreateParams
import com.stripe.android.paymentsheet.paymentdatacollection.FormArguments
import com.stripe.android.uicore.elements.FormElement

internal data class PaymentMethodState(
val isProcessing: Boolean,
val formArguments: FormArguments,
val formElements: List<FormElement>,
val primaryButtonState: PrimaryButtonState,
val primaryButtonLabel: ResolvableString,
val paymentSelection: PaymentSelection? = null
val paymentMethodCreateParams: PaymentMethodCreateParams? = null,
val errorMessage: ResolvableString? = null
)
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,43 @@ package com.stripe.android.link.ui.paymentmenthod

import androidx.lifecycle.ViewModel
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.link.LinkActivityResult
import com.stripe.android.link.LinkConfiguration
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.ui.PrimaryButtonState
import com.stripe.android.link.ui.completePaymentButtonLabel
import com.stripe.android.lpmfoundations.paymentmethod.PaymentMethodMetadata
import com.stripe.android.model.ConsumerPaymentDetails
import com.stripe.android.model.PaymentMethod
import com.stripe.android.paymentsheet.DefaultFormHelper
import com.stripe.android.paymentsheet.FormHelper
import com.stripe.android.paymentsheet.forms.FormFieldValues
import com.stripe.android.paymentsheet.model.PaymentSelection
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import javax.inject.Inject

internal class PaymentMethodViewModel @Inject constructor(
private val configuration: LinkConfiguration,
private val formHelperFactory: (UpdateSelection) -> FormHelper
private val linkAccount: LinkAccount,
private val linkAccountManager: LinkAccountManager,
private val linkConfirmationHandler: LinkConfirmationHandler,
private val logger: Logger,
private val formHelper: FormHelper,
private val dismissWithResult: (LinkActivityResult) -> Unit
) : ViewModel() {
private val formHelper = formHelperFactory(::updateSelection)
private val _state = MutableStateFlow(
PaymentMethodState(
isProcessing = false,
formElements = formHelper.formElementsForCode(PaymentMethod.Type.Card.code),
formArguments = formHelper.createFormArguments(PaymentMethod.Type.Card.code),
primaryButtonState = PrimaryButtonState.Disabled,
Expand All @@ -37,17 +49,14 @@ internal class PaymentMethodViewModel @Inject constructor(
val state: StateFlow<PaymentMethodState> = _state

fun formValuesChanged(formValues: FormFieldValues?) {
formHelper.onFormFieldValuesChanged(
val params = formHelper.getPaymentMethodParams(
formValues = formValues,
selectedPaymentMethodCode = PaymentMethod.Type.Card.code
)
}

private fun updateSelection(selection: PaymentSelection?) {
_state.update {
it.copy(
paymentSelection = selection,
primaryButtonState = if (selection != null) {
paymentMethodCreateParams = params,
primaryButtonState = if (params != null) {
PrimaryButtonState.Enabled
} else {
PrimaryButtonState.Disabled
Expand All @@ -56,23 +65,83 @@ internal class PaymentMethodViewModel @Inject constructor(
}
}

fun onPayClicked() {
val paymentMethodCreateParams = _state.value.paymentMethodCreateParams
if (paymentMethodCreateParams == null) {
logger.error("PaymentMethodViewModel: onPayClicked without paymentMethodCreateParams")
return
}
viewModelScope.launch {
updateButtonState(PrimaryButtonState.Processing)
linkAccountManager.createCardPaymentDetails(paymentMethodCreateParams)
.fold(
onSuccess = { linkPaymentDetails ->
performConfirmation(linkPaymentDetails.paymentDetails)
},
onFailure = { error ->
_state.update {
it.copy(
errorMessage = error.stripeErrorMessage()
)
}
logger.error(
msg = "PaymentMethodViewModel: Failed to create card payment details",
t = error
)
}
)
}
updateButtonState(PrimaryButtonState.Enabled)
}

private suspend fun performConfirmation(paymentDetails: ConsumerPaymentDetails.PaymentDetails) {
val result = linkConfirmationHandler.confirm(
paymentDetails = paymentDetails,
linkAccount = linkAccount,
cvc = null
)
when (result) {
Result.Canceled -> Unit
is Result.Failed -> {
_state.update { it.copy(errorMessage = result.message) }
}
Result.Succeeded -> {
dismissWithResult(LinkActivityResult.Completed)
}
}
}

private fun updateButtonState(state: PrimaryButtonState) {
_state.update {
it.copy(
primaryButtonState = state
)
}
}

companion object {
fun factory(
parentComponent: NativeLinkComponent
parentComponent: NativeLinkComponent,
linkAccount: LinkAccount,
dismissWithResult: (LinkActivityResult) -> Unit
): ViewModelProvider.Factory {
return viewModelFactory {
initializer {
PaymentMethodViewModel(
configuration = parentComponent.configuration,
formHelperFactory = { selectionUpdater ->
DefaultFormHelper.create(
cardAccountRangeRepositoryFactory = parentComponent.cardAccountRangeRepositoryFactory,
paymentMethodMetadata = PaymentMethodMetadata.create(
configuration = parentComponent.configuration,
),
selectionUpdater = selectionUpdater
)
}
linkAccount = linkAccount,
linkAccountManager = parentComponent.linkAccountManager,
linkConfirmationHandler = parentComponent.linkConfirmationHandlerFactory.create(
confirmationHandler = parentComponent.viewModel.confirmationHandler
),
formHelper = DefaultFormHelper.create(
cardAccountRangeRepositoryFactory = parentComponent.cardAccountRangeRepositoryFactory,
paymentMethodMetadata = PaymentMethodMetadata.create(
configuration = parentComponent.configuration,
),
),
logger = parentComponent.logger,
dismissWithResult = dismissWithResult
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ import com.stripe.android.lpmfoundations.paymentmethod.PaymentMethodMetadata
import com.stripe.android.lpmfoundations.paymentmethod.UiDefinitionFactory
import com.stripe.android.model.PaymentMethod
import com.stripe.android.model.PaymentMethodCode
import com.stripe.android.model.PaymentMethodCreateParams
import com.stripe.android.paymentsheet.forms.FormArgumentsFactory
import com.stripe.android.paymentsheet.forms.FormFieldValues
import com.stripe.android.paymentsheet.model.PaymentSelection
import com.stripe.android.paymentsheet.paymentdatacollection.FormArguments
import com.stripe.android.paymentsheet.ui.transformToPaymentMethodCreateParams
import com.stripe.android.paymentsheet.ui.transformToPaymentSelection
import com.stripe.android.paymentsheet.viewmodels.BaseSheetViewModel
import com.stripe.android.uicore.elements.FormElement
Expand Down Expand Up @@ -40,22 +42,21 @@ internal class DefaultFormHelper(
onLinkInlineSignupStateChanged = linkInlineHandler::onStateUpdated,
selectionUpdater = {
viewModel.updateSelection(it)
}
},
)
}

fun create(
cardAccountRangeRepositoryFactory: CardAccountRangeRepository.Factory,
paymentMethodMetadata: PaymentMethodMetadata,
selectionUpdater: (PaymentSelection?) -> Unit,
): FormHelper {
return DefaultFormHelper(
cardAccountRangeRepositoryFactory = cardAccountRangeRepositoryFactory,
paymentMethodMetadata = paymentMethodMetadata,
newPaymentSelectionProvider = { null },
linkConfigurationCoordinator = null,
onLinkInlineSignupStateChanged = {},
selectionUpdater = selectionUpdater
selectionUpdater = {},
)
}
}
Expand Down Expand Up @@ -92,6 +93,16 @@ internal class DefaultFormHelper(
selectionUpdater(newSelection)
}

override fun getPaymentMethodParams(
formValues: FormFieldValues?,
selectedPaymentMethodCode: String
): PaymentMethodCreateParams? {
return formValues?.transformToPaymentMethodCreateParams(
paymentMethodCode = selectedPaymentMethodCode,
paymentMethodMetadata = paymentMethodMetadata
)
}

override fun requiresFormScreen(selectedPaymentMethodCode: String): Boolean {
val userInteractionAllowed = formElementsForCode(selectedPaymentMethodCode).any { it.allowsUserInteraction }
return userInteractionAllowed ||
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.stripe.android.paymentsheet

import com.stripe.android.model.PaymentMethodCode
import com.stripe.android.model.PaymentMethodCreateParams
import com.stripe.android.paymentsheet.forms.FormFieldValues
import com.stripe.android.paymentsheet.paymentdatacollection.FormArguments
import com.stripe.android.uicore.elements.FormElement
Expand All @@ -15,5 +16,10 @@ internal interface FormHelper {

fun onFormFieldValuesChanged(formValues: FormFieldValues?, selectedPaymentMethodCode: String)

fun getPaymentMethodParams(
formValues: FormFieldValues?,
selectedPaymentMethodCode: String
): PaymentMethodCreateParams?

fun requiresFormScreen(selectedPaymentMethodCode: String): Boolean
}
Loading

0 comments on commit ac56735

Please sign in to comment.