From df88d90d68657f4b63c9e07b3a58b0b0e129042c Mon Sep 17 00:00:00 2001 From: Samer Alabi Date: Thu, 5 Dec 2024 19:13:27 -0500 Subject: [PATCH] Better handle process death for all confirmation flow cases in `DefaultConfirmationHandler` --- .../DefaultConfirmationHandler.kt | 19 +++++-- .../DefaultConfirmationHandlerTest.kt | 51 ++++++++++++++++++- .../paymentsheet/PaymentSheetViewModelTest.kt | 1 + 3 files changed, 65 insertions(+), 6 deletions(-) diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentelement/confirmation/DefaultConfirmationHandler.kt b/paymentsheet/src/main/java/com/stripe/android/paymentelement/confirmation/DefaultConfirmationHandler.kt index 0e98d181952..63ceaf4ba42 100644 --- a/paymentsheet/src/main/java/com/stripe/android/paymentelement/confirmation/DefaultConfirmationHandler.kt +++ b/paymentsheet/src/main/java/com/stripe/android/paymentelement/confirmation/DefaultConfirmationHandler.kt @@ -28,12 +28,12 @@ internal class DefaultConfirmationHandler( private val savedStateHandle: SavedStateHandle, private val errorReporter: ErrorReporter, ) : ConfirmationHandler { - private val isAwaitingForResultData = retrieveIsAwaitingForResultData() + private val isInitiallyAwaitingForResultData = retrieveIsAwaitingForResultData() - override val hasReloadedFromProcessDeath = isAwaitingForResultData != null + override val hasReloadedFromProcessDeath = isInitiallyAwaitingForResultData != null private val _state = MutableStateFlow( - isAwaitingForResultData?.let { data -> + isInitiallyAwaitingForResultData?.let { data -> ConfirmationHandler.State.Confirming(data.confirmationOption) } ?: ConfirmationHandler.State.Idle ) @@ -44,9 +44,12 @@ internal class DefaultConfirmationHandler( coroutineScope.launch { delay(1.seconds) + val isStillAwaitingForResultData = retrieveIsAwaitingForResultData() + if ( - _state.value is ConfirmationHandler.State.Confirming && - isAwaitingForResultData?.receivesResultInProcess != true + isStillAwaitingForResultData != null && + isStillAwaitingForResultData.key == isInitiallyAwaitingForResultData?.key && + !isStillAwaitingForResultData.receivesResultInProcess ) { onHandlerResult( ConfirmationHandler.Result.Canceled( @@ -142,6 +145,7 @@ internal class DefaultConfirmationHandler( when (val action = mediator.action(confirmationOption, intent)) { is ConfirmationMediator.Action.Launch -> { storeIsAwaitingForResult( + key = mediator.key, option = confirmationOption, receivesResultInProcess = action.receivesResultInProcess, ) @@ -171,6 +175,8 @@ internal class DefaultConfirmationHandler( private fun onResult(result: ConfirmationDefinition.Result) { val confirmationResult = when (result) { is ConfirmationDefinition.Result.NextStep -> { + removeIsAwaitingForResult() + coroutineScope.launch { confirm( intent = result.intent, @@ -204,10 +210,12 @@ internal class DefaultConfirmationHandler( } private fun storeIsAwaitingForResult( + key: String, option: ConfirmationHandler.Option, receivesResultInProcess: Boolean, ) { savedStateHandle[AWAITING_CONFIRMATION_RESULT_KEY] = AwaitingConfirmationResultData( + key = key, confirmationOption = option, receivesResultInProcess = receivesResultInProcess, ) @@ -229,6 +237,7 @@ internal class DefaultConfirmationHandler( @Parcelize data class AwaitingConfirmationResultData( + val key: String, val confirmationOption: ConfirmationHandler.Option, /* * Indicates the user receives the result within the process of the app. For example, Bacs & Google Pay open diff --git a/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/DefaultConfirmationHandlerTest.kt b/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/DefaultConfirmationHandlerTest.kt index b65ed07ffa0..798ebf257b7 100644 --- a/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/DefaultConfirmationHandlerTest.kt +++ b/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/DefaultConfirmationHandlerTest.kt @@ -462,6 +462,54 @@ class DefaultConfirmationHandlerTest { } } + @Test + fun `On return from process death, should continue to confirm is next step result is received`() { + val dispatcher = StandardTestDispatcher() + + test( + savedStateHandle = createPrepopulatedSavedStateHandle(receivesResultInProcess = false), + someDefinitionResult = ConfirmationDefinition.Result.NextStep( + intent = PAYMENT_INTENT, + confirmationOption = SomeOtherConfirmationDefinition.Option, + ), + someOtherDefinitionAction = ConfirmationDefinition.Action.Launch( + launcherArguments = SomeOtherConfirmationDefinition.LauncherArgs, + receivesResultInProcess = false, + deferredIntentConfirmationType = null, + ), + someOtherDefinitionResult = ConfirmationDefinition.Result.Succeeded( + intent = UPDATED_PAYMENT_INTENT, + deferredIntentConfirmationType = null, + ), + dispatcher = dispatcher, + ) { + assertThat(confirmationHandler.hasReloadedFromProcessDeath).isTrue() + + confirmationHandler.state.test { + assertSomeDefinitionConfirmingState() + + sendSomeDefinitionLauncherResult() + assertSomeDefinitionToResultCalled() + + dispatcher.scheduler.advanceUntilIdle() + + assertSomeOtherDefinitionConfirmingState() + assertSomeOtherDefinitionActionCalled() + assertSomeOtherDefinitionLaunchCalled() + sendSomeOtherDefinitionLauncherResult() + assertSomeOtherDefinitionToResultCalled() + + val completeState = awaitCompleteState() + val successResult = completeState.result.assertSucceeded() + + assertThat(successResult.intent).isEqualTo(UPDATED_PAYMENT_INTENT) + assertThat(successResult.deferredIntentConfirmationType).isNull() + + confirmationHandler.assertAwaitResultCallReceivesSameResult(completeState) + } + } + } + @Test fun `On 'awaitResult', should return null if no confirmation process was started`() = test { val result = withTimeout(5.seconds) { @@ -731,6 +779,7 @@ class DefaultConfirmationHandlerTest { set( "AwaitingConfirmationResult", DefaultConfirmationHandler.AwaitingConfirmationResultData( + key = "Some", confirmationOption = SomeConfirmationDefinition.Option, receivesResultInProcess = receivesResultInProcess, ), @@ -955,7 +1004,7 @@ class DefaultConfirmationHandlerTest { action = action, result = result, ) { - override val key: String = "Some" + override val key: String = "SomeOther" override fun option(confirmationOption: ConfirmationHandler.Option): Option? { return confirmationOption as? Option diff --git a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/PaymentSheetViewModelTest.kt b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/PaymentSheetViewModelTest.kt index d5df7baa324..12b165469bd 100644 --- a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/PaymentSheetViewModelTest.kt +++ b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/PaymentSheetViewModelTest.kt @@ -2931,6 +2931,7 @@ internal class PaymentSheetViewModelTest { val savedStateHandle = SavedStateHandle( initialState = mapOf( "AwaitingConfirmationResult" to DefaultConfirmationHandler.AwaitingConfirmationResultData( + key = "Intent", confirmationOption = option, receivesResultInProcess = false, ),