-
Notifications
You must be signed in to change notification settings - Fork 729
Expand file tree
/
Copy pathCheckoutConfirmationResultHandler.kt
More file actions
77 lines (69 loc) · 3.5 KB
/
Copy pathCheckoutConfirmationResultHandler.kt
File metadata and controls
77 lines (69 loc) · 3.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
package com.stripe.android.checkout
import com.stripe.android.core.injection.ViewModelScope
import com.stripe.android.paymentelement.CheckoutSessionPreview
import com.stripe.android.paymentelement.confirmation.ConfirmationHandler
import com.stripe.android.paymentelement.confirmation.intent.CheckoutSessionResponseKey
import com.stripe.android.paymentsheet.repositories.CheckoutSessionResponse
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import javax.inject.Inject
@OptIn(CheckoutSessionPreview::class)
internal class CheckoutConfirmationResultHandler @Inject constructor(
private val confirmationHandler: ConfirmationHandler,
private val resultCallback: CheckoutController.ResultCallback,
@ViewModelScope private val viewModelScope: CoroutineScope,
) {
private var isAwaitingProcessDeathResult = confirmationHandler.hasReloadedFromProcessDeath
/**
* Observes the shared [ConfirmationHandler] for the controller's lifetime and forwards each
* terminal confirmation result to [resultCallback]. This is registered exactly once (from
* [CheckoutController]'s init); the handler emits [ConfirmationHandler.State.Complete] at most once
* per confirmation and never restores a stale completion across process death, so this single
* collector delivers each result exactly once.
*/
fun register(
onSucceeded: suspend (CheckoutSessionResponse?) -> Unit,
) {
confirmationHandler.state
.filterIsInstance<ConfirmationHandler.State.Complete>()
.onEach { handle(it.result, onSucceeded) }
.launchIn(viewModelScope)
}
private suspend fun handle(
result: ConfirmationHandler.Result,
onSucceeded: suspend (CheckoutSessionResponse?) -> Unit,
) {
val isProcessDeathResult = isAwaitingProcessDeathResult
isAwaitingProcessDeathResult = false
when (result) {
is ConfirmationHandler.Result.Succeeded -> handleSucceeded(result, onSucceeded)
is ConfirmationHandler.Result.Failed ->
resultCallback.onResult(CheckoutController.Result.Failed(result.cause))
is ConfirmationHandler.Result.Canceled -> handleCanceled(result, isProcessDeathResult)
}
}
private suspend fun handleSucceeded(
result: ConfirmationHandler.Result.Succeeded,
onSucceeded: suspend (CheckoutSessionResponse?) -> Unit,
) {
// Synchronous confirmation carries the latest response. Next-action confirmation does not,
// so the controller retrieves the completed Checkout Session before delivering the result.
onSucceeded(result.metadata[CheckoutSessionResponseKey])
resultCallback.onResult(CheckoutController.Result.Completed())
}
private fun handleCanceled(
result: ConfirmationHandler.Result.Canceled,
isProcessDeathResult: Boolean,
) {
// None normally keeps the customer in the payment UI. After process death, however, it means
// the pending next-action result did not return, so checkout must report the interrupted flow.
val shouldInformMerchant =
result.action == ConfirmationHandler.Result.Canceled.Action.InformCancellation ||
result.action == ConfirmationHandler.Result.Canceled.Action.None && isProcessDeathResult
if (shouldInformMerchant) {
resultCallback.onResult(CheckoutController.Result.Canceled())
}
}
}