Skip to content

Commit

Permalink
Add CustomerSheet playground UI
Browse files Browse the repository at this point in the history
  • Loading branch information
samer-stripe committed May 23, 2024
1 parent 98455e1 commit 993f71c
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.stripe.android.paymentsheet.example.playground

import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.painter.Painter
import com.stripe.android.paymentsheet.model.PaymentOption

internal data class CustomerSheetState(
val selectedPaymentOption: PaymentOption? = null,
val shouldFetchPaymentOption: Boolean = true
)

internal fun CustomerSheetState?.paymentMethodLabel(): String {
return this?.selectedPaymentOption?.label ?: "Select"
}

@Composable
internal fun CustomerSheetState?.paymentMethodPainter(): Painter? {
return this?.selectedPaymentOption?.iconPainter
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.stripe.android.customersheet.ExperimentalCustomerSheetApi
import com.stripe.android.customersheet.rememberCustomerSheet
import com.stripe.android.model.PaymentMethod
import com.stripe.android.paymentsheet.ExternalPaymentMethodConfirmHandler
import com.stripe.android.paymentsheet.PaymentSheet
Expand Down Expand Up @@ -244,7 +246,14 @@ internal class PaymentSheetPlaygroundActivity : AppCompatActivity(), ExternalPay
}
}
is PlaygroundState.Customer -> {
// TODO(samer-stripe): Implement Customer Sheet UI
val customerSheetState by viewModel.customerSheetState.collectAsState()

customerSheetState?.let { state ->
CustomerSheetUi(
customerSheetState = state,
playgroundState = playgroundState
)
}
}
}
}
Expand Down Expand Up @@ -308,6 +317,34 @@ internal class PaymentSheetPlaygroundActivity : AppCompatActivity(), ExternalPay
)
}

@OptIn(ExperimentalCustomerSheetApi::class)
@Composable
fun CustomerSheetUi(
playgroundState: PlaygroundState.Customer,
customerSheetState: CustomerSheetState,
) {
val customerSheet = rememberCustomerSheet(
configuration = playgroundState.customerSheetConfiguration(),
customerAdapter = playgroundState.adapter,
callback = viewModel::onCustomerSheetCallback
)

LaunchedEffect(customerSheet) {
viewModel.fetchOption(customerSheet)
}

if (customerSheetState.shouldFetchPaymentOption) {
return
}

PaymentMethodSelector(
isEnabled = true,
paymentMethodLabel = customerSheetState.paymentMethodLabel(),
paymentMethodPainter = customerSheetState.paymentMethodPainter(),
onClick = customerSheet::present
)
}

@Composable
private fun ShippingAddressButton(
addressLauncher: AddressLauncher,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import com.github.kittinunf.result.Result
import com.stripe.android.PaymentConfiguration
import com.stripe.android.customersheet.CustomerAdapter
import com.stripe.android.customersheet.CustomerEphemeralKey
import com.stripe.android.customersheet.CustomerSheet
import com.stripe.android.customersheet.CustomerSheetResult
import com.stripe.android.customersheet.ExperimentalCustomerSheetApi
import com.stripe.android.model.PaymentMethod
import com.stripe.android.paymentsheet.CreateIntentResult
Expand Down Expand Up @@ -59,6 +61,7 @@ internal class PaymentSheetPlaygroundViewModel(
val status = MutableStateFlow<StatusMessage?>(null)
val state = MutableStateFlow<PlaygroundState?>(null)
val flowControllerState = MutableStateFlow<FlowControllerState?>(null)
val customerSheetState = MutableStateFlow<CustomerSheetState?>(null)

init {
viewModelScope.launch(Dispatchers.IO) {
Expand All @@ -73,6 +76,7 @@ internal class PaymentSheetPlaygroundViewModel(
) {
state.value = null
flowControllerState.value = null
customerSheetState.value = null

if (playgroundSettings.configurationData.value.integrationType.isPaymentFlow()) {
prepareCheckout(playgroundSettings)
Expand Down Expand Up @@ -158,6 +162,7 @@ internal class PaymentSheetPlaygroundViewModel(
}
)
)
customerSheetState.value = CustomerSheetState()
}
}

Expand Down Expand Up @@ -301,6 +306,66 @@ internal class PaymentSheetPlaygroundViewModel(
status.value = StatusMessage(statusMessage)
}

@OptIn(ExperimentalCustomerSheetApi::class)
fun fetchOption(customerSheet: CustomerSheet) {
viewModelScope.launch(Dispatchers.IO) {
when (val result = customerSheet.retrievePaymentOptionSelection()) {
is CustomerSheetResult.Selected -> {
customerSheetState.update { existingState ->
existingState?.copy(
selectedPaymentOption = result.selection?.paymentOption,
shouldFetchPaymentOption = false,
)
}
}
is CustomerSheetResult.Failed -> {
customerSheetState.update { existingState ->
existingState?.copy(
shouldFetchPaymentOption = false,
)
}

status.emit(
StatusMessage(
message = "Failed to retrieve payment options:\n${result.exception.message}"
)
)
}
is CustomerSheetResult.Canceled -> {
customerSheetState.update { existingState ->
existingState?.copy(
shouldFetchPaymentOption = false,
)
}
}
}
}
}

@OptIn(ExperimentalCustomerSheetApi::class)
fun onCustomerSheetCallback(result: CustomerSheetResult) {
val statusMessage = when (result) {
is CustomerSheetResult.Selected -> {
customerSheetState.update { existingState ->
existingState?.copy(
selectedPaymentOption = result.selection?.paymentOption,
shouldFetchPaymentOption = false
)
}

null
}
is CustomerSheetResult.Failed -> "An error occurred: ${result.exception.message}"
is CustomerSheetResult.Canceled -> "Canceled"
}

statusMessage?.let { message ->
viewModelScope.launch {
status.emit(StatusMessage(message))
}
}
}

private fun createIntent(clientSecret: String): CreateIntentResult {
// Note: This is not how you'd do this in a real application. Instead, your app would
// call your backend and create (and optionally confirm) a payment or setup intent.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ internal object CountrySettingsDefinition :
PlaygroundSettingDefinition.Displayable<Country> {
private val supportedPaymentFlowCountries = Country.entries.map { it.value }.toSet()
private val supportedCustomerFlowCountries = setOf(
Country.US,
Country.FR,
Country.US.value,
Country.FR.value,
)

override val displayName: String = "Merchant"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,10 @@ private fun IntegrationTypeConfigurableSetting(
name = "Flow Controller",
value = PlaygroundConfigurationData.IntegrationType.FlowController
),
PlaygroundSettingDefinition.Displayable.Option(
name = "Customer Sheet",
value = PlaygroundConfigurationData.IntegrationType.CustomerSheet
),
),
value = configurationData.integrationType
) { integrationType ->
Expand Down

0 comments on commit 993f71c

Please sign in to comment.