diff --git a/paymentsheet-example/src/main/java/com/stripe/android/paymentsheet/example/playground/CustomerSheetState.kt b/paymentsheet-example/src/main/java/com/stripe/android/paymentsheet/example/playground/CustomerSheetState.kt new file mode 100644 index 00000000000..824a3421e8a --- /dev/null +++ b/paymentsheet-example/src/main/java/com/stripe/android/paymentsheet/example/playground/CustomerSheetState.kt @@ -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 +} diff --git a/paymentsheet-example/src/main/java/com/stripe/android/paymentsheet/example/playground/PaymentSheetPlaygroundActivity.kt b/paymentsheet-example/src/main/java/com/stripe/android/paymentsheet/example/playground/PaymentSheetPlaygroundActivity.kt index 4a796d14e65..54c6fdd0a1e 100644 --- a/paymentsheet-example/src/main/java/com/stripe/android/paymentsheet/example/playground/PaymentSheetPlaygroundActivity.kt +++ b/paymentsheet-example/src/main/java/com/stripe/android/paymentsheet/example/playground/PaymentSheetPlaygroundActivity.kt @@ -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 @@ -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 + ) + } } } } @@ -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, diff --git a/paymentsheet-example/src/main/java/com/stripe/android/paymentsheet/example/playground/PaymentSheetPlaygroundViewModel.kt b/paymentsheet-example/src/main/java/com/stripe/android/paymentsheet/example/playground/PaymentSheetPlaygroundViewModel.kt index b12e74ecff2..5e7e4383f9d 100644 --- a/paymentsheet-example/src/main/java/com/stripe/android/paymentsheet/example/playground/PaymentSheetPlaygroundViewModel.kt +++ b/paymentsheet-example/src/main/java/com/stripe/android/paymentsheet/example/playground/PaymentSheetPlaygroundViewModel.kt @@ -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 @@ -59,6 +61,7 @@ internal class PaymentSheetPlaygroundViewModel( val status = MutableStateFlow(null) val state = MutableStateFlow(null) val flowControllerState = MutableStateFlow(null) + val customerSheetState = MutableStateFlow(null) init { viewModelScope.launch(Dispatchers.IO) { @@ -73,6 +76,7 @@ internal class PaymentSheetPlaygroundViewModel( ) { state.value = null flowControllerState.value = null + customerSheetState.value = null if (playgroundSettings.configurationData.value.integrationType.isPaymentFlow()) { prepareCheckout(playgroundSettings) @@ -158,6 +162,7 @@ internal class PaymentSheetPlaygroundViewModel( } ) ) + customerSheetState.value = CustomerSheetState() } } @@ -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. diff --git a/paymentsheet-example/src/main/java/com/stripe/android/paymentsheet/example/playground/settings/CountrySettingsDefinition.kt b/paymentsheet-example/src/main/java/com/stripe/android/paymentsheet/example/playground/settings/CountrySettingsDefinition.kt index c93dab44f30..43cd89be76f 100644 --- a/paymentsheet-example/src/main/java/com/stripe/android/paymentsheet/example/playground/settings/CountrySettingsDefinition.kt +++ b/paymentsheet-example/src/main/java/com/stripe/android/paymentsheet/example/playground/settings/CountrySettingsDefinition.kt @@ -15,8 +15,8 @@ internal object CountrySettingsDefinition : PlaygroundSettingDefinition.Displayable { 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" diff --git a/paymentsheet-example/src/main/java/com/stripe/android/paymentsheet/example/playground/settings/SettingsUI.kt b/paymentsheet-example/src/main/java/com/stripe/android/paymentsheet/example/playground/settings/SettingsUI.kt index 9f738d87cdb..f1ec56c63f4 100644 --- a/paymentsheet-example/src/main/java/com/stripe/android/paymentsheet/example/playground/settings/SettingsUI.kt +++ b/paymentsheet-example/src/main/java/com/stripe/android/paymentsheet/example/playground/settings/SettingsUI.kt @@ -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 ->