Script – Checkout Flow
fun NavFlow<OrderOutput, *>.launchCheckoutScript(
scope: CoroutineScope,
repositories: CheckoutRepositories,
onTrace: (String) -> Unit = { }
) = launchNavFlowScript(scope, onTrace = onTrace) {
showRoot { CartReviewNode(scope) }
trace { "Checkout: showing cart" }
awaitOutputOfType<OrderOutput.CartConfirmed>()
showRoot { ShippingAddressNode(scope) }
val address = awaitOutputOfType<OrderOutput.AddressEntered>()
repositories.address.save(address.value)
showRoot { PaymentNode(scope) }
val paymentResult = awaitOutputCase<PaymentAction> {
on<OrderOutput.PaymentAuthorized> { PaymentAction.Success(it.paymentId) }
on<OrderOutput.PaymentFailed> { PaymentAction.Failure(it.reason) }
}
when (paymentResult) {
is PaymentAction.Success -> {
trace { "Checkout: payment authorized ${paymentResult.id}" }
showRoot { ConfirmationNode(scope, paymentResult.id) }
}
is PaymentAction.Failure -> {
trace { "Checkout: payment failure ${paymentResult.reason}" }
pushNode { PaymentErrorNode(scope, paymentResult.reason) }
awaitOutputOfType<OrderOutput.PaymentRetry>()
navFlow.pop()
replaceTop { PaymentNode(scope) }
}
}
}
When to use:
- Multi-step checkout/wizard flows where policy, retries, and tracing belong in one place.
Why it matters:
- Centralises checkout policy (cart → shipping → payment → confirmation/retry) in one coroutine.
- Shows tracing, branching, and reuse of helpers (
awaitOutputCase,trace,pushNode). - Nodes stay focused on UI/state; payment retry/branch logic lives here and is easy to test headlessly.