Code

Update CartViewModel.kt — payment in two phases

In the code column — the full CartViewModel.kt. It's grown since lesson 3: 3 → in-memory cart, 9 → placeOrder, 10 → the table. Now — payment in two phases. Note the order: startPayment() only fetches a clientSecret; the order (placeOrder) is recorded only in completeOrder(), once Stripe has confirmed the payment.

CartEvent gains payment events:

sealed interface CartEvent {
    // ...earlier ones (AddItem, RemoveItem, ...)
    data object Checkout : CartEvent            // tapped "Pay"
    data object PaymentSucceeded : CartEvent    // Stripe: success
    data object PaymentCanceled : CartEvent     // user canceled
    data class PaymentFailed(val message: String?) : CartEvent
    data object ClearOrderPlaced : CartEvent
}

PicaApi gains the endpoint (the server sets the amount in cents):

@POST("create-payment-intent")
suspend fun createPaymentIntent(@Body request: PaymentIntentRequest): PaymentIntentResponse

@Serializable data class PaymentIntentRequest(@SerialName("amount_cents") val amountCents: Int)
@Serializable data class PaymentIntentResponse(@SerialName("client_secret") val clientSecret: String)

And Koin — CartViewModel now has three dependencies:

// cartModule
single { OrderRepository(get()) }
viewModel { CartViewModel(get(), get(), get()) }   // OrderRepository, PicaApi, TableSession

PaymentConfiguration runs once in PicaApplication.onCreate (before any PaymentSheet):

PaymentConfiguration.init(applicationContext, "pk_test_...")   // your Stripe publishable key

Tip. Why are startPayment and completeOrder separate? Because between them there's a user action (entering the card in PaymentSheet) and a network round-trip to Stripe. The ViewModel can't "await" payment inline — it launches PaymentSheet (via clientSecret) and reacts to the result as a new event (PaymentSucceeded). A classic async UI flow.