Skip to content

Commit

Permalink
Better handle process death for all confirmation flow cases in `Defau…
Browse files Browse the repository at this point in the history
…ltConfirmationHandler` (#9754)
  • Loading branch information
samer-stripe authored Dec 6, 2024
1 parent ab9e133 commit 2b36058
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Expand All @@ -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(
Expand Down Expand Up @@ -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,
)
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
)
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -731,6 +779,7 @@ class DefaultConfirmationHandlerTest {
set(
"AwaitingConfirmationResult",
DefaultConfirmationHandler.AwaitingConfirmationResultData(
key = "Some",
confirmationOption = SomeConfirmationDefinition.Option,
receivesResultInProcess = receivesResultInProcess,
),
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2931,6 +2931,7 @@ internal class PaymentSheetViewModelTest {
val savedStateHandle = SavedStateHandle(
initialState = mapOf(
"AwaitingConfirmationResult" to DefaultConfirmationHandler.AwaitingConfirmationResultData(
key = "Intent",
confirmationOption = option,
receivesResultInProcess = false,
),
Expand Down

0 comments on commit 2b36058

Please sign in to comment.