diff --git a/Demo/src/main/java/com/paypal/android/api/model/Amount.kt b/Demo/src/main/java/com/paypal/android/api/model/Amount.kt deleted file mode 100644 index c13fe1273..000000000 --- a/Demo/src/main/java/com/paypal/android/api/model/Amount.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.paypal.android.api.model - -import com.google.gson.annotations.SerializedName - -data class Amount( - @SerializedName("currency_code") - val currencyCode: String?, - val value: String? -) diff --git a/Demo/src/main/java/com/paypal/android/api/model/ApplicationContext.kt b/Demo/src/main/java/com/paypal/android/api/model/ApplicationContext.kt deleted file mode 100644 index b2f30d913..000000000 --- a/Demo/src/main/java/com/paypal/android/api/model/ApplicationContext.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.paypal.android.api.model - -import com.google.gson.annotations.SerializedName - -data class ApplicationContext( - - @SerializedName("return_url") - val returnURL: String, - - @SerializedName("cancel_url") - val cancelURL: String -) diff --git a/Demo/src/main/java/com/paypal/android/api/model/Payee.kt b/Demo/src/main/java/com/paypal/android/api/model/Payee.kt deleted file mode 100644 index a663fd9f5..000000000 --- a/Demo/src/main/java/com/paypal/android/api/model/Payee.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.paypal.android.api.model - -import com.google.gson.annotations.SerializedName - -data class Payee( - @SerializedName("email_address") - val emailAddress: String? -) diff --git a/Demo/src/main/java/com/paypal/android/api/model/PurchaseUnit.kt b/Demo/src/main/java/com/paypal/android/api/model/PurchaseUnit.kt deleted file mode 100644 index c47b62c7e..000000000 --- a/Demo/src/main/java/com/paypal/android/api/model/PurchaseUnit.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.paypal.android.api.model - -data class PurchaseUnit( - val amount: Amount -) diff --git a/Demo/src/main/java/com/paypal/android/ui/createorder/CreateOrderFragment.kt b/Demo/src/main/java/com/paypal/android/ui/createorder/CreateOrderFragment.kt deleted file mode 100644 index be386ae93..000000000 --- a/Demo/src/main/java/com/paypal/android/ui/createorder/CreateOrderFragment.kt +++ /dev/null @@ -1,132 +0,0 @@ -package com.paypal.android.ui.createorder - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.ViewGroup -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.ComposeView -import androidx.compose.ui.platform.ViewCompositionStrategy -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.fragment.app.Fragment -import androidx.fragment.app.viewModels -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.lifecycle.lifecycleScope -import androidx.navigation.NavDirections -import androidx.navigation.fragment.findNavController -import androidx.navigation.fragment.navArgs -import com.paypal.android.models.OrderRequest -import com.paypal.android.ui.features.Feature -import com.paypal.android.uishared.components.CreateOrderWithVaultOptionForm -import com.paypal.android.usecase.CreateOrderUseCase -import dagger.hilt.android.AndroidEntryPoint -import kotlinx.coroutines.launch -import javax.inject.Inject - -@AndroidEntryPoint -class CreateOrderFragment : Fragment() { - - @Inject - lateinit var createOrderUseCase: CreateOrderUseCase - - private val args: CreateOrderFragmentArgs by navArgs() - private val viewModel by viewModels() - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ) = ComposeView(requireContext()).apply { - setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) - setContent { - val uiState by viewModel.uiState.collectAsStateWithLifecycle() - MaterialTheme { - Surface(modifier = Modifier.fillMaxSize()) { - CreateOrderView( - feature = args.feature, - uiState = uiState, - onCreateOrderClick = { - createOrder() - } - ) - } - } - } - } - - private fun createOrder() { - viewLifecycleOwner.lifecycleScope.launch { - viewModel.isLoading = true - - val uiState = viewModel.uiState.value - val orderRequest = uiState.run { OrderRequest(intentOption, shouldVault, customerId) } - val order = createOrderUseCase(orderRequest) - - viewModel.isLoading = false - - // TODO: remove once Feature enum is converted to an inner class of FeaturesFragment - // continue on to feature - when (args.feature) { - Feature.PAYPAL_NATIVE -> { - navigate( - CreateOrderFragmentDirections.actionCreateOrderFragmentToPayPalNativeFragment( - order - ) - ) - } - - else -> {} - } - } - } - - private fun navigate(action: NavDirections) { - findNavController().navigate(action) - } - - @Composable - fun CreateOrderView( - feature: Feature, - uiState: CreateOrderUiState, - onCreateOrderClick: () -> Unit - ) { - Column( - modifier = Modifier - .padding(16.dp) - ) { - CreateOrderWithVaultOptionForm( - title = "Create an order to proceed with ${stringResource(feature.stringRes)}:", - orderIntent = uiState.intentOption, - shouldVault = uiState.shouldVault, - vaultCustomerId = uiState.customerId, - isLoading = uiState.isLoading, - onIntentOptionSelected = { value -> viewModel.intentOption = value }, - onShouldVaultChanged = { value -> viewModel.shouldVault = value }, - onVaultCustomerIdChanged = { value -> viewModel.customerId = value }, - onSubmit = { onCreateOrderClick() } - ) - } - } - - @Preview - @Composable - fun CreateOrderViewPreview() { - MaterialTheme { - Surface(modifier = Modifier.fillMaxSize()) { - CreateOrderView( - feature = Feature.CARD_APPROVE_ORDER, - uiState = CreateOrderUiState(), - onCreateOrderClick = {} - ) - } - } - } -} diff --git a/Demo/src/main/java/com/paypal/android/ui/createorder/CreateOrderUiState.kt b/Demo/src/main/java/com/paypal/android/ui/createorder/CreateOrderUiState.kt deleted file mode 100644 index 9c1906c72..000000000 --- a/Demo/src/main/java/com/paypal/android/ui/createorder/CreateOrderUiState.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.paypal.android.ui.createorder - -import com.paypal.android.api.model.OrderIntent - -data class CreateOrderUiState( - val intentOption: OrderIntent = OrderIntent.AUTHORIZE, - val isLoading: Boolean = false, - val shouldVault: Boolean = false, - val customerId: String = "", -) diff --git a/Demo/src/main/java/com/paypal/android/ui/createorder/CreateOrderViewModel.kt b/Demo/src/main/java/com/paypal/android/ui/createorder/CreateOrderViewModel.kt deleted file mode 100644 index 99fc4cc08..000000000 --- a/Demo/src/main/java/com/paypal/android/ui/createorder/CreateOrderViewModel.kt +++ /dev/null @@ -1,36 +0,0 @@ -package com.paypal.android.ui.createorder - -import androidx.lifecycle.ViewModel -import com.paypal.android.api.model.OrderIntent -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.update - -class CreateOrderViewModel : ViewModel() { - private val _uiState = MutableStateFlow(CreateOrderUiState()) - val uiState = _uiState.asStateFlow() - - var intentOption: OrderIntent - get() = _uiState.value.intentOption - set(value) { - _uiState.update { it.copy(intentOption = value) } - } - - var shouldVault: Boolean - get() = _uiState.value.shouldVault - set(value) { - _uiState.update { it.copy(shouldVault = value) } - } - - var customerId: String - get() = _uiState.value.customerId - set(value) { - _uiState.update { it.copy(customerId = value) } - } - - var isLoading: Boolean - get() = _uiState.value.isLoading - set(value) { - _uiState.update { it.copy(isLoading = value) } - } -} diff --git a/Demo/src/main/java/com/paypal/android/ui/features/Feature.kt b/Demo/src/main/java/com/paypal/android/ui/features/Feature.kt deleted file mode 100644 index 4f9f0ab1e..000000000 --- a/Demo/src/main/java/com/paypal/android/ui/features/Feature.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.paypal.android.ui.features - -import android.os.Parcelable -import androidx.annotation.StringRes -import com.paypal.android.R -import kotlinx.parcelize.Parcelize - -// TODO: make this enum an inner class of FeaturesFragment -@Parcelize -enum class Feature(@StringRes val stringRes: Int) : Parcelable { - CARD_APPROVE_ORDER(R.string.feature_approve_order), - CARD_VAULT(R.string.feature_vault), - PAYPAL_WEB(R.string.feature_paypal_web), - PAYPAL_BUTTONS(R.string.feature_paypal_buttons), - PAYPAL_NATIVE(R.string.feature_paypal_native) -} diff --git a/Demo/src/main/java/com/paypal/android/ui/features/FeaturesFragment.kt b/Demo/src/main/java/com/paypal/android/ui/features/FeaturesFragment.kt index 4a021fa74..17fb0deec 100644 --- a/Demo/src/main/java/com/paypal/android/ui/features/FeaturesFragment.kt +++ b/Demo/src/main/java/com/paypal/android/ui/features/FeaturesFragment.kt @@ -3,6 +3,7 @@ package com.paypal.android.ui.features import android.os.Bundle import android.view.LayoutInflater import android.view.ViewGroup +import androidx.annotation.StringRes import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -45,6 +46,14 @@ class FeaturesFragment : Fragment() { val chevronTint = Color(138, 137, 142) } + enum class Feature(@StringRes val stringRes: Int) { + CARD_APPROVE_ORDER(R.string.feature_approve_order), + CARD_VAULT(R.string.feature_vault), + PAYPAL_WEB(R.string.feature_paypal_web), + PAYPAL_BUTTONS(R.string.feature_paypal_buttons), + PAYPAL_NATIVE(R.string.feature_paypal_native) + } + private val cardFeatures = listOf( Feature.CARD_APPROVE_ORDER, Feature.CARD_VAULT @@ -86,16 +95,19 @@ class FeaturesFragment : Fragment() { Feature.PAYPAL_BUTTONS -> { FeaturesFragmentDirections.actionPaymentMethodsFragmentToPayPalButtonsFragment() } + Feature.PAYPAL_WEB -> { FeaturesFragmentDirections.actionPaymentMethodsFragmentToPayPalWebFragment() } + Feature.PAYPAL_NATIVE -> { - FeaturesFragmentDirections.actionPaymentMethodsFragmentToCreateOrderFragment(feature) + FeaturesFragmentDirections.actionPaymentMethodsFragmentToPayPalNativeFragment() } Feature.CARD_APPROVE_ORDER -> { FeaturesFragmentDirections.actionPaymentMethodsFragmentToCardFragment() } + Feature.CARD_VAULT -> { FeaturesFragmentDirections.actionPaymentMethodsFragmentToVaultFragment() } diff --git a/Demo/src/main/java/com/paypal/android/ui/paypal/PayPalNativeFragment.kt b/Demo/src/main/java/com/paypal/android/ui/paypal/PayPalNativeFragment.kt index 5e3c10edf..2d161a24e 100644 --- a/Demo/src/main/java/com/paypal/android/ui/paypal/PayPalNativeFragment.kt +++ b/Demo/src/main/java/com/paypal/android/ui/paypal/PayPalNativeFragment.kt @@ -1,224 +1,149 @@ package com.paypal.android.ui.paypal import android.os.Bundle -import android.util.Log import android.view.LayoutInflater -import android.view.View import android.view.ViewGroup -import android.widget.RadioButton -import android.widget.Toast -import androidx.core.view.isVisible +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.ViewCompositionStrategy +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.lifecycle.lifecycleScope import com.paypal.android.R -import com.paypal.android.api.model.OrderIntent import com.paypal.android.api.services.SDKSampleServerAPI -import com.paypal.android.databinding.FragmentPayPalNativeBinding -import com.paypal.android.viewmodels.NativeCheckoutViewState -import com.paypal.android.viewmodels.PayPalNativeViewModel +import com.paypal.android.ui.paypalweb.PayPalWebCheckoutCanceledView +import com.paypal.android.ui.paypalweb.PayPalWebCheckoutResultView +import com.paypal.android.uishared.components.CompleteOrderForm +import com.paypal.android.uishared.components.CreateOrderWithShippingPreferenceForm +import com.paypal.android.uishared.components.OrderView +import com.paypal.android.uishared.components.PayPalSDKErrorView +import com.paypal.android.usecase.GetOrderUseCase import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.launch import javax.inject.Inject @AndroidEntryPoint @Suppress("TooManyFunctions") class PayPalNativeFragment : Fragment() { - private lateinit var binding: FragmentPayPalNativeBinding - @Inject lateinit var sdkSampleServerAPI: SDKSampleServerAPI - private var selectedShippingPreference: ShippingPreferenceType? = null + @Inject + lateinit var getOrderUseCase: GetOrderUseCase private val viewModel: PayPalNativeViewModel by viewModels() - private val orderIntent: OrderIntent - get() = when (binding.radioGroupIntent.checkedRadioButtonId) { - R.id.intent_authorize -> OrderIntent.AUTHORIZE - else -> OrderIntent.CAPTURE - } - override fun onCreateView( - inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? - ): View { - binding = FragmentPayPalNativeBinding.inflate(inflater, container, false) - viewModel.state.observe(viewLifecycleOwner) { viewState -> - checkViewState(viewState) - } - with(binding) { - startNativeCheckout.setOnClickListener { startCheckout() } - fetchClientIdButton.setOnClickListener { viewModel.fetchClientId() } - tryAgainButton.setOnClickListener { viewModel.reset() } - } - initShippingOptions() - return binding.root - } - - private fun startCheckout() { - selectedShippingPreference?.let { - viewModel.orderIdCheckout(it, orderIntent) - } - binding.checkoutOptionsRadioGroup.isVisible = false - binding.radioGroupIntent.isVisible = false - } - - private fun initShippingOptions() { - ShippingPreferenceType.values().forEach { shippingPreferenceType -> - val radioButton = RadioButton(requireContext()) - radioButton.text = shippingPreferenceType.description - radioButton.setOnClickListener { - selectedShippingPreference = shippingPreferenceType - binding.startNativeCheckout.isEnabled = true + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ) = ComposeView(requireContext()).apply { + setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) + setContent { + val uiState by viewModel.uiState.collectAsStateWithLifecycle() + MaterialTheme { + Surface(modifier = Modifier.fillMaxSize()) { + PayPalNativeView( + uiState, + onCreateOrderClick = { createOrder() }, + onCompleteOrderClick = { viewModel.completeOrder() } + ) + } } - binding.checkoutOptionsRadioGroup.addView(radioButton) - } - } - - private fun checkViewState(viewState: NativeCheckoutViewState) { - when (viewState) { - NativeCheckoutViewState.Initial -> setInitialState() - NativeCheckoutViewState.CheckoutInit -> checkoutInit() - NativeCheckoutViewState.CheckoutCancelled -> checkoutCancelled() - is NativeCheckoutViewState.CheckoutComplete -> checkoutComplete(viewState) - is NativeCheckoutViewState.CheckoutError -> checkoutError(viewState) - NativeCheckoutViewState.CheckoutStart -> checkoutStart() - NativeCheckoutViewState.FetchingClientId -> generatingToken() - is NativeCheckoutViewState.OrderCreated -> orderCreated(viewState) - is NativeCheckoutViewState.ClientIdFetched -> clientIdFetched(viewState) - NativeCheckoutViewState.OrderPatched -> orderPatched() - is NativeCheckoutViewState.OrderCaptured -> orderCaptured(viewState) - is NativeCheckoutViewState.OrderAuthorized -> orderAuthorized(viewState) - } - } - - private fun setInitialState() { - with(binding) { - fetchClientIdButton.visibility = View.VISIBLE - fetchClientIdButton.isEnabled = true - startNativeCheckout.visibility = View.GONE - contentGroup.visibility = View.GONE - tryAgainButton.visibility = View.GONE - hideProgress() - } - } - - private fun generatingToken() { - showProgress(getString(R.string.fetching_client_id)) - with(binding) { - fetchClientIdButton.isEnabled = false - startNativeCheckout.visibility = View.GONE - } - } - - private fun clientIdFetched(viewState: NativeCheckoutViewState.ClientIdFetched) { - hideProgress() - setContent(getString(R.string.client_id_fetched), viewState.token) - with(binding) { - startNativeCheckout.visibility = View.VISIBLE - fetchClientIdButton.visibility = View.GONE - checkoutOptionsRadioGroup.clearCheck() - checkoutOptionsRadioGroup.isVisible = true - radioGroupIntent.isVisible = true - } - } - - private fun setContent(titleText: String, contentText: String) { - with(binding) { - contentGroup.visibility = View.VISIBLE - title.text = titleText - content.text = contentText - } - } - - private fun checkoutInit() { - showProgress(getString(R.string.init_checkout)) - with(binding) { - startNativeCheckout.isEnabled = false } } - private fun checkoutStart() { - showProgress(getString(R.string.starting_paypal)) - } - - private fun checkoutError(viewState: NativeCheckoutViewState.CheckoutError) { - val message = - viewState.message ?: viewState.error?.reason ?: getString(R.string.something_went_wrong) - setContent(getString(R.string.error), message) - hideProgress() - with(binding) { - startNativeCheckout.visibility = View.GONE - tryAgainButton.visibility = View.VISIBLE - } - } + private fun createOrder() { + viewLifecycleOwner.lifecycleScope.launch { + viewModel.isCreateOrderLoading = true - private fun orderCreated(viewState: NativeCheckoutViewState.OrderCreated) { - setContent(getString(R.string.order_created), "OrderId: ${viewState.orderId}") - hideProgress() - } + val shippingPreference = viewModel.shippingPreference + val orderIntent = viewModel.intentOption + viewModel.createdOrder = getOrderUseCase(shippingPreference, orderIntent) - private fun orderCaptured(viewState: NativeCheckoutViewState.OrderCaptured) { - val contentText = viewState.order.run { "OrderId: $id Status: $status Intent: CAPTURE" } - setContent(getString(R.string.order_created), contentText) - hideProgress() - } - - private fun orderAuthorized(viewState: NativeCheckoutViewState.OrderAuthorized) { - val contentText = viewState.order.run { "OrderId: $id Status: $status Intent: AUTHORIZE" } - setContent(getString(R.string.order_created), contentText) - hideProgress() - } - - private fun checkoutCancelled() { - setContent(getString(R.string.cancelled), getString(R.string.checkout_cancelled_by_user)) - hideProgress() - with(binding) { - startNativeCheckout.visibility = View.GONE - tryAgainButton.visibility = View.VISIBLE + viewModel.isCreateOrderLoading = false } } - private fun checkoutComplete(viewState: NativeCheckoutViewState.CheckoutComplete) { - val content = "Order Id: ${viewState.orderId} \n" + "Payer Id: ${viewState.payerId} \n" - setContent(getString(R.string.approved), content) - hideProgress() - with(binding) { - tryAgainButton.visibility = View.VISIBLE - startNativeCheckout.visibility = View.GONE - } - viewState.orderId?.let { orderId -> - when (orderIntent) { - OrderIntent.CAPTURE -> { - showProgress("Capturing Order...") - viewModel.captureOrder(orderId) - } - - OrderIntent.AUTHORIZE -> { - showProgress("Authorizing Order...") - viewModel.authorizeOrder(orderId) - } + private fun startCheckout() { + viewLifecycleOwner.lifecycleScope.launch { + viewModel.startNativeCheckout() + } + } + + @Composable + fun PayPalNativeView( + uiState: PayPalNativeUiState, + onCreateOrderClick: () -> Unit, + onCompleteOrderClick: () -> Unit + ) { + val scrollState = rememberScrollState() + LaunchedEffect(uiState) { + // continuously scroll to bottom of the list when event state is updated + scrollState.animateScrollTo(scrollState.maxValue) + } + Column( + modifier = Modifier + .padding(16.dp) + .verticalScroll(scrollState) + ) { + CreateOrderWithShippingPreferenceForm( + title = "Create an order to proceed with ${stringResource(R.string.feature_paypal_native)}:", + orderIntent = uiState.intentOption, + shippingPreference = uiState.shippingPreference, + isLoading = uiState.isCreateOrderLoading, + onIntentOptionSelected = { value -> viewModel.intentOption = value }, + onShippingPreferenceSelected = { value -> viewModel.shippingPreference = value }, + onSubmit = { onCreateOrderClick() } + ) + uiState.createdOrder?.let { createdOrder -> + Spacer(modifier = Modifier.size(24.dp)) + OrderView(order = createdOrder, title = "Order Created") + Spacer(modifier = Modifier.size(24.dp)) + StartPayPalNativeCheckoutForm( + isLoading = uiState.isStartCheckoutLoading, + onSubmit = { startCheckout() } + ) } + uiState.payPalNativeCheckoutResult?.let { result -> + Spacer(modifier = Modifier.size(24.dp)) + PayPalWebCheckoutResultView(result.orderId, result.payerId) + Spacer(modifier = Modifier.size(24.dp)) + CompleteOrderForm( + isLoading = uiState.isCompleteOrderLoading, + orderIntent = uiState.intentOption, + onSubmit = { onCompleteOrderClick() } + ) + } + uiState.payPalNativeCheckoutError?.let { error -> + Spacer(modifier = Modifier.size(24.dp)) + PayPalSDKErrorView(error = error) + } + if (uiState.isCheckoutCanceled) { + Spacer(modifier = Modifier.size(24.dp)) + PayPalWebCheckoutCanceledView() + } + uiState.completedOrder?.let { completedOrder -> + Spacer(modifier = Modifier.size(24.dp)) + OrderView(order = completedOrder, title = "Order Completed") + } + Spacer(modifier = Modifier.size(24.dp)) } } - - private fun showProgress(text: String) { - with(binding) { - progressGroup.visibility = View.VISIBLE - progressText.text = text - contentGroup.visibility = View.GONE - } - } - - private fun hideProgress() { - binding.progressGroup.visibility = View.GONE - } - - private fun orderPatched() { - Toast.makeText(requireContext(), "Order Patched", Toast.LENGTH_SHORT).show() - Log.d(TAG, "Patch Order after shipping change was successful") - } - - companion object { - private val TAG = PayPalNativeFragment::class.java.simpleName - } } diff --git a/Demo/src/main/java/com/paypal/android/ui/paypal/PayPalNativeUiState.kt b/Demo/src/main/java/com/paypal/android/ui/paypal/PayPalNativeUiState.kt new file mode 100644 index 000000000..86c588bfb --- /dev/null +++ b/Demo/src/main/java/com/paypal/android/ui/paypal/PayPalNativeUiState.kt @@ -0,0 +1,19 @@ +package com.paypal.android.ui.paypal + +import com.paypal.android.api.model.Order +import com.paypal.android.api.model.OrderIntent +import com.paypal.android.corepayments.PayPalSDKError +import com.paypal.android.paypalnativepayments.PayPalNativeCheckoutResult + +data class PayPalNativeUiState( + val intentOption: OrderIntent = OrderIntent.AUTHORIZE, + val isCreateOrderLoading: Boolean = false, + val isCompleteOrderLoading: Boolean = false, + val createdOrder: Order? = null, + val completedOrder: Order? = null, + val shippingPreference: ShippingPreferenceType = ShippingPreferenceType.GET_FROM_FILE, + val isStartCheckoutLoading: Boolean = false, + val isCheckoutCanceled: Boolean = false, + val payPalNativeCheckoutResult: PayPalNativeCheckoutResult? = null, + val payPalNativeCheckoutError: PayPalSDKError? = null +) diff --git a/Demo/src/main/java/com/paypal/android/ui/paypal/PayPalNativeViewModel.kt b/Demo/src/main/java/com/paypal/android/ui/paypal/PayPalNativeViewModel.kt new file mode 100644 index 000000000..8e84472f1 --- /dev/null +++ b/Demo/src/main/java/com/paypal/android/ui/paypal/PayPalNativeViewModel.kt @@ -0,0 +1,209 @@ +package com.paypal.android.ui.paypal + +import android.app.Application +import android.widget.Toast +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.viewModelScope +import com.paypal.android.BuildConfig +import com.paypal.android.api.model.Order +import com.paypal.android.api.model.OrderIntent +import com.paypal.android.api.services.SDKSampleServerAPI +import com.paypal.android.corepayments.CoreConfig +import com.paypal.android.corepayments.PayPalSDKError +import com.paypal.android.fraudprotection.PayPalDataCollector +import com.paypal.android.paypalnativepayments.PayPalNativeCheckoutClient +import com.paypal.android.paypalnativepayments.PayPalNativeCheckoutListener +import com.paypal.android.paypalnativepayments.PayPalNativeCheckoutRequest +import com.paypal.android.paypalnativepayments.PayPalNativeCheckoutResult +import com.paypal.android.paypalnativepayments.PayPalNativePaysheetActions +import com.paypal.android.paypalnativepayments.PayPalNativeShippingAddress +import com.paypal.android.paypalnativepayments.PayPalNativeShippingListener +import com.paypal.android.paypalnativepayments.PayPalNativeShippingMethod +import com.paypal.android.usecase.CompleteOrderUseCase +import com.paypal.android.usecase.GetClientIdUseCase +import com.paypal.android.usecase.GetOrderUseCase +import com.paypal.android.usecase.UpdateOrderUseCase +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import okio.IOException +import javax.inject.Inject + +@HiltViewModel +class PayPalNativeViewModel @Inject constructor( + application: Application +) : AndroidViewModel(application) { + + @Inject + lateinit var getClientIdUseCase: GetClientIdUseCase + + @Inject + lateinit var getOrderUseCase: GetOrderUseCase + + @Inject + lateinit var completeOrderUseCase: CompleteOrderUseCase + + @Inject + lateinit var updateOrderUseCase: UpdateOrderUseCase + + @Inject + lateinit var sdkSampleServerAPI: SDKSampleServerAPI + + private var orderId: String? = null + + private val _uiState = MutableStateFlow(PayPalNativeUiState()) + val uiState = _uiState.asStateFlow() + + var intentOption: OrderIntent + get() = _uiState.value.intentOption + set(value) { + _uiState.update { it.copy(intentOption = value) } + } + + var isCreateOrderLoading: Boolean + get() = _uiState.value.isCreateOrderLoading + set(value) { + _uiState.update { it.copy(isCreateOrderLoading = value) } + } + + var isStartCheckoutLoading: Boolean + get() = _uiState.value.isStartCheckoutLoading + set(value) { + _uiState.update { it.copy(isStartCheckoutLoading = value) } + } + + var isCheckoutCanceled: Boolean + get() = _uiState.value.isCheckoutCanceled + set(value) { + _uiState.update { it.copy(isCheckoutCanceled = value) } + } + + var isCompleteOrderLoading: Boolean + get() = _uiState.value.isCompleteOrderLoading + set(value) { + _uiState.update { it.copy(isCompleteOrderLoading = value) } + } + + var createdOrder: Order? + get() = _uiState.value.createdOrder + set(value) { + _uiState.update { it.copy(createdOrder = value) } + } + + var completedOrder: Order? + get() = _uiState.value.completedOrder + set(value) { + _uiState.update { it.copy(completedOrder = value) } + } + + var shippingPreference: ShippingPreferenceType + get() = _uiState.value.shippingPreference + set(value) { + _uiState.update { it.copy(shippingPreference = value) } + } + + var payPalNativeCheckoutResult: PayPalNativeCheckoutResult? + get() = _uiState.value.payPalNativeCheckoutResult + set(value) { + _uiState.update { it.copy(payPalNativeCheckoutResult = value) } + } + + var payPalNativeCheckoutError: PayPalSDKError? + get() = _uiState.value.payPalNativeCheckoutError + set(value) { + _uiState.update { it.copy(payPalNativeCheckoutError = value) } + } + + private val payPalListener = object : PayPalNativeCheckoutListener { + override fun onPayPalCheckoutStart() { + isStartCheckoutLoading = true + } + + override fun onPayPalCheckoutSuccess(result: PayPalNativeCheckoutResult) { + isStartCheckoutLoading = false + payPalNativeCheckoutResult = result + } + + override fun onPayPalCheckoutFailure(error: PayPalSDKError) { + isStartCheckoutLoading = false + payPalNativeCheckoutError = error + } + + override fun onPayPalCheckoutCanceled() { + isStartCheckoutLoading = false + isCheckoutCanceled = true + } + } + + private val shippingListener = object : PayPalNativeShippingListener { + + override fun onPayPalNativeShippingAddressChange( + actions: PayPalNativePaysheetActions, + shippingAddress: PayPalNativeShippingAddress + ) { + if (shippingAddress.adminArea1.isNullOrBlank() || shippingAddress.adminArea1 == "NV") { + actions.reject() + } else { + actions.approve() + } + } + + override fun onPayPalNativeShippingMethodChange( + actions: PayPalNativePaysheetActions, + shippingMethod: PayPalNativeShippingMethod + ) { + + viewModelScope.launch(exceptionHandler) { + orderId?.also { + try { + updateOrderUseCase(it, shippingMethod) + actions.approve() + } catch (e: IOException) { + actions.reject() + throw e + } + } + } + } + } + + private lateinit var payPalClient: PayPalNativeCheckoutClient + private lateinit var payPalDataCollector: PayPalDataCollector + + private val exceptionHandler = CoroutineExceptionHandler { _, e -> + // TODO: show error in UI using a Compose UI Alert Dialog to improve error messaging UX + Toast.makeText(getApplication(), e.message, Toast.LENGTH_LONG).show() + } + + suspend fun startNativeCheckout() { + val clientId = getClientIdUseCase() + + val coreConfig = CoreConfig(clientId) + val returnUrl = "${BuildConfig.APPLICATION_ID}://paypalpay" + payPalClient = PayPalNativeCheckoutClient(getApplication(), coreConfig, returnUrl) + payPalClient.listener = payPalListener + payPalClient.shippingListener = shippingListener + + payPalDataCollector = PayPalDataCollector(coreConfig) + + createdOrder?.id?.also { orderId -> + payPalClient.startCheckout(PayPalNativeCheckoutRequest(orderId)) + } + } + + fun completeOrder() { + viewModelScope.launch { + isCompleteOrderLoading = true + + val cmid = payPalDataCollector.collectDeviceData(getApplication()) + val orderId = createdOrder!!.id!! + val orderIntent = intentOption + + completedOrder = completeOrderUseCase(orderId, orderIntent, cmid) + isCompleteOrderLoading = false + } + } +} diff --git a/Demo/src/main/java/com/paypal/android/ui/paypal/ShippingPreferenceType.kt b/Demo/src/main/java/com/paypal/android/ui/paypal/ShippingPreferenceType.kt index f5864cafd..6fa3a7ac1 100644 --- a/Demo/src/main/java/com/paypal/android/ui/paypal/ShippingPreferenceType.kt +++ b/Demo/src/main/java/com/paypal/android/ui/paypal/ShippingPreferenceType.kt @@ -5,11 +5,5 @@ import com.paypal.checkout.createorder.ShippingPreference enum class ShippingPreferenceType { GET_FROM_FILE, NO_SHIPPING, SET_PROVIDED_ADDRESS; - val description - get() = when (this) { - GET_FROM_FILE -> "Get From File" - NO_SHIPPING -> "No Shipping" - SET_PROVIDED_ADDRESS -> "Set Provided Address" - } val nxoShippingPreference = ShippingPreference.valueOf(this.toString()) } diff --git a/Demo/src/main/java/com/paypal/android/ui/paypal/StartPayPalNativeCheckoutForm.kt b/Demo/src/main/java/com/paypal/android/ui/paypal/StartPayPalNativeCheckoutForm.kt new file mode 100644 index 000000000..2c35b6cfb --- /dev/null +++ b/Demo/src/main/java/com/paypal/android/ui/paypal/StartPayPalNativeCheckoutForm.kt @@ -0,0 +1,41 @@ +package com.paypal.android.ui.paypal + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedCard +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.paypal.android.R +import com.paypal.android.ui.WireframeButton + +@Composable +fun StartPayPalNativeCheckoutForm( + isLoading: Boolean, + onSubmit: () -> Unit +) { + + OutlinedCard(modifier = Modifier.fillMaxWidth()) { + Column(modifier = Modifier.padding(8.dp)) { + Text( + text = "Launch PayPal Native Checkout", + style = MaterialTheme.typography.titleLarge + ) + Spacer(modifier = Modifier.size(16.dp)) + WireframeButton( + text = stringResource(R.string.start_checkout), + isLoading = isLoading, + onClick = { onSubmit() }, + modifier = Modifier + .fillMaxWidth() + .padding(top = 16.dp) + ) + } + } +} diff --git a/Demo/src/main/java/com/paypal/android/ui/paypalweb/PayPalWebCheckoutResultView.kt b/Demo/src/main/java/com/paypal/android/ui/paypalweb/PayPalWebCheckoutResultView.kt index 07f4aea45..95d78b2a8 100644 --- a/Demo/src/main/java/com/paypal/android/ui/paypalweb/PayPalWebCheckoutResultView.kt +++ b/Demo/src/main/java/com/paypal/android/ui/paypalweb/PayPalWebCheckoutResultView.kt @@ -11,19 +11,18 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.paypal.android.R -import com.paypal.android.paypalwebpayments.PayPalWebCheckoutResult import com.paypal.android.uishared.components.PropertyView @Composable -fun PayPalWebCheckoutResultView(result: PayPalWebCheckoutResult) { +fun PayPalWebCheckoutResultView(orderId: String?, payerId: String?) { OutlinedCard(modifier = Modifier.fillMaxWidth()) { Column(modifier = Modifier.padding(8.dp)) { Text( text = stringResource(id = R.string.order_approved), style = MaterialTheme.typography.titleLarge ) - PropertyView(name = "Order ID", value = result.orderId) - PropertyView(name = "Payer ID", value = result.payerId) + PropertyView(name = "Order ID", value = orderId) + PropertyView(name = "Payer ID", value = payerId) } } } diff --git a/Demo/src/main/java/com/paypal/android/ui/paypalweb/PayPalWebFragment.kt b/Demo/src/main/java/com/paypal/android/ui/paypalweb/PayPalWebFragment.kt index cf297b5a1..2f9291cad 100644 --- a/Demo/src/main/java/com/paypal/android/ui/paypalweb/PayPalWebFragment.kt +++ b/Demo/src/main/java/com/paypal/android/ui/paypalweb/PayPalWebFragment.kt @@ -204,7 +204,7 @@ class PayPalWebFragment : Fragment(), PayPalWebCheckoutListener { } uiState.payPalWebCheckoutResult?.let { result -> Spacer(modifier = Modifier.size(24.dp)) - PayPalWebCheckoutResultView(result = result) + PayPalWebCheckoutResultView(result.orderId, result.payerId) Spacer(modifier = Modifier.size(24.dp)) CompleteOrderForm( isLoading = uiState.isCompleteOrderLoading, diff --git a/Demo/src/main/java/com/paypal/android/uishared/components/CreateOrderWithShippingPreferenceForm.kt b/Demo/src/main/java/com/paypal/android/uishared/components/CreateOrderWithShippingPreferenceForm.kt new file mode 100644 index 000000000..880d5f5cc --- /dev/null +++ b/Demo/src/main/java/com/paypal/android/uishared/components/CreateOrderWithShippingPreferenceForm.kt @@ -0,0 +1,109 @@ +package com.paypal.android.uishared.components + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedCard +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.paypal.android.R +import com.paypal.android.api.model.OrderIntent +import com.paypal.android.ui.OptionList +import com.paypal.android.ui.WireframeButton +import com.paypal.android.ui.paypal.ShippingPreferenceType + +@Suppress("CyclomaticComplexMethod") +@Composable +fun CreateOrderWithShippingPreferenceForm( + title: String, + orderIntent: OrderIntent, + shippingPreference: ShippingPreferenceType, + isLoading: Boolean, + onIntentOptionSelected: (OrderIntent) -> Unit = {}, + onShippingPreferenceSelected: (ShippingPreferenceType) -> Unit = {}, + onSubmit: () -> Unit = {} +) { + val captureValue = stringResource(id = R.string.intent_capture) + val authorizeValue = stringResource(id = R.string.intent_authorize) + val selectedOrderIntent = when (orderIntent) { + OrderIntent.CAPTURE -> captureValue + OrderIntent.AUTHORIZE -> authorizeValue + } + + val getFromFileValue = stringResource(R.string.shipping_preference_get_from_file) + val noShippingValue = stringResource(R.string.shipping_preference_no_shipping) + val setProvidedAddressValue = stringResource(R.string.shipping_preference_set_provided_address) + + val selectedShippingPreference = when (shippingPreference) { + ShippingPreferenceType.GET_FROM_FILE -> getFromFileValue + ShippingPreferenceType.NO_SHIPPING -> noShippingValue + ShippingPreferenceType.SET_PROVIDED_ADDRESS -> setProvidedAddressValue + } + OutlinedCard(modifier = Modifier.fillMaxWidth()) { + Column(modifier = Modifier.padding(8.dp)) { + Text(text = title, style = MaterialTheme.typography.titleLarge) + Spacer(modifier = Modifier.size(16.dp)) + OptionList( + title = stringResource(id = R.string.intent_title), + options = listOf(authorizeValue, captureValue), + selectedOption = selectedOrderIntent, + onOptionSelected = { option -> + val newOrderIntent = when (option) { + captureValue -> OrderIntent.CAPTURE + authorizeValue -> OrderIntent.CAPTURE + else -> null + } + newOrderIntent?.let { onIntentOptionSelected(it) } + } + ) + Spacer(modifier = Modifier.size(16.dp)) + OptionList( + title = stringResource(id = R.string.shipping_preference), + options = listOf(getFromFileValue, noShippingValue, setProvidedAddressValue), + selectedOption = selectedShippingPreference, + onOptionSelected = { option -> + val newShippingPreferenceValue = when (option) { + getFromFileValue -> ShippingPreferenceType.GET_FROM_FILE + noShippingValue -> ShippingPreferenceType.NO_SHIPPING + setProvidedAddressValue -> ShippingPreferenceType.SET_PROVIDED_ADDRESS + else -> null + } + newShippingPreferenceValue?.let { onShippingPreferenceSelected(it) } + } + ) + Spacer(modifier = Modifier.size(16.dp)) + WireframeButton( + text = "Create Order", + isLoading = isLoading, + onClick = { onSubmit() }, + modifier = Modifier + .fillMaxWidth() + .padding(top = 16.dp) + ) + } + } +} + +@Preview +@Composable +fun CreateOrderWithShippingPreferenceFormPreview() { + MaterialTheme { + Surface(modifier = Modifier.fillMaxSize()) { + CreateOrderWithShippingPreferenceForm( + title = "Sample Title", + orderIntent = OrderIntent.AUTHORIZE, + shippingPreference = ShippingPreferenceType.NO_SHIPPING, + isLoading = false + ) + } + } +} diff --git a/Demo/src/main/java/com/paypal/android/usecase/GetOrderIdUseCase.kt b/Demo/src/main/java/com/paypal/android/usecase/GetOrderUseCase.kt similarity index 56% rename from Demo/src/main/java/com/paypal/android/usecase/GetOrderIdUseCase.kt rename to Demo/src/main/java/com/paypal/android/usecase/GetOrderUseCase.kt index 475730aef..f5f203327 100644 --- a/Demo/src/main/java/com/paypal/android/usecase/GetOrderIdUseCase.kt +++ b/Demo/src/main/java/com/paypal/android/usecase/GetOrderUseCase.kt @@ -1,5 +1,6 @@ package com.paypal.android.usecase +import com.paypal.android.api.model.Order import com.paypal.android.api.model.OrderIntent import com.paypal.android.api.services.SDKSampleServerAPI import com.paypal.android.ui.paypal.ShippingPreferenceType @@ -8,21 +9,19 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import javax.inject.Inject -class GetOrderIdUseCase @Inject constructor( +class GetOrderUseCase @Inject constructor( private val sdkSampleServerAPI: SDKSampleServerAPI ) { suspend operator fun invoke( shippingPreferenceType: ShippingPreferenceType, orderIntent: OrderIntent - ): String? = - withContext(Dispatchers.IO) { - val order = OrderUtils.createOrderBuilder( - "5.0", - orderIntent = orderIntent, - shippingPreference = shippingPreferenceType.nxoShippingPreference - ) - val result = sdkSampleServerAPI.createOrder(order) - result.id - } + ): Order = withContext(Dispatchers.IO) { + val order = OrderUtils.createOrderBuilder( + "5.0", + orderIntent = orderIntent, + shippingPreference = shippingPreferenceType.nxoShippingPreference + ) + sdkSampleServerAPI.createOrder(order) + } } diff --git a/Demo/src/main/java/com/paypal/android/viewmodels/NativeCheckoutViewState.kt b/Demo/src/main/java/com/paypal/android/viewmodels/NativeCheckoutViewState.kt deleted file mode 100644 index 56f023e67..000000000 --- a/Demo/src/main/java/com/paypal/android/viewmodels/NativeCheckoutViewState.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.paypal.android.viewmodels - -import com.paypal.android.api.model.Order -import com.paypal.checkout.error.ErrorInfo - -sealed class NativeCheckoutViewState { - object Initial : NativeCheckoutViewState() - object FetchingClientId : NativeCheckoutViewState() - class ClientIdFetched(val token: String) : NativeCheckoutViewState() - class OrderCreated(val orderId: String) : NativeCheckoutViewState() - object CheckoutInit : NativeCheckoutViewState() - object CheckoutStart : NativeCheckoutViewState() - object CheckoutCancelled : NativeCheckoutViewState() - object OrderPatched : NativeCheckoutViewState() - class OrderCaptured(val order: Order) : NativeCheckoutViewState() - class OrderAuthorized(val order: Order) : NativeCheckoutViewState() - class CheckoutError(val message: String? = null, val error: ErrorInfo? = null) : - NativeCheckoutViewState() - - data class CheckoutComplete( - val payerId: String?, - val orderId: String? - ) : NativeCheckoutViewState() -} diff --git a/Demo/src/main/java/com/paypal/android/viewmodels/PayPalNativeViewModel.kt b/Demo/src/main/java/com/paypal/android/viewmodels/PayPalNativeViewModel.kt deleted file mode 100644 index 723e25f70..000000000 --- a/Demo/src/main/java/com/paypal/android/viewmodels/PayPalNativeViewModel.kt +++ /dev/null @@ -1,171 +0,0 @@ -package com.paypal.android.viewmodels - -import android.app.Application -import androidx.lifecycle.AndroidViewModel -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.viewModelScope -import com.paypal.android.BuildConfig -import com.paypal.android.api.model.OrderIntent -import com.paypal.android.corepayments.CoreConfig -import com.paypal.android.corepayments.PayPalSDKError -import com.paypal.android.paypalnativepayments.PayPalNativeCheckoutClient -import com.paypal.android.paypalnativepayments.PayPalNativeCheckoutError -import com.paypal.android.paypalnativepayments.PayPalNativeCheckoutListener -import com.paypal.android.paypalnativepayments.PayPalNativeCheckoutRequest -import com.paypal.android.paypalnativepayments.PayPalNativeCheckoutResult -import com.paypal.android.paypalnativepayments.PayPalNativePaysheetActions -import com.paypal.android.paypalnativepayments.PayPalNativeShippingAddress -import com.paypal.android.paypalnativepayments.PayPalNativeShippingListener -import com.paypal.android.paypalnativepayments.PayPalNativeShippingMethod -import com.paypal.android.ui.paypal.ShippingPreferenceType -import com.paypal.android.usecase.CompleteOrderUseCase -import com.paypal.android.usecase.GetClientIdUseCase -import com.paypal.android.usecase.GetOrderIdUseCase -import com.paypal.android.usecase.UpdateOrderUseCase -import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.CoroutineExceptionHandler -import kotlinx.coroutines.launch -import okio.IOException -import javax.inject.Inject - -@HiltViewModel -class PayPalNativeViewModel @Inject constructor( - application: Application -) : AndroidViewModel(application) { - - @Inject - lateinit var getClientIdUseCase: GetClientIdUseCase - - @Inject - lateinit var getOrderIdUseCase: GetOrderIdUseCase - - @Inject - lateinit var completeOrderUseCase: CompleteOrderUseCase - - @Inject - lateinit var updateOrderUseCase: UpdateOrderUseCase - - private var orderId: String? = null - - private val payPalListener = object : PayPalNativeCheckoutListener { - override fun onPayPalCheckoutStart() { - internalState.postValue(NativeCheckoutViewState.CheckoutStart) - } - - override fun onPayPalCheckoutSuccess(result: PayPalNativeCheckoutResult) { - result.apply { - internalState.postValue( - NativeCheckoutViewState.CheckoutComplete( - payerId, - orderId - ) - ) - } - } - - override fun onPayPalCheckoutFailure(error: PayPalSDKError) { - val nxoError = error.cause as? PayPalNativeCheckoutError - val errorState = if (nxoError != null) { - NativeCheckoutViewState.CheckoutError(error = nxoError.errorInfo) - } else { - NativeCheckoutViewState.CheckoutError(message = error.errorDescription) - } - internalState.postValue(errorState) - } - - override fun onPayPalCheckoutCanceled() { - internalState.postValue(NativeCheckoutViewState.CheckoutCancelled) - } - } - - private val shippingListener = object : PayPalNativeShippingListener { - - override fun onPayPalNativeShippingAddressChange( - actions: PayPalNativePaysheetActions, - shippingAddress: PayPalNativeShippingAddress - ) { - if (shippingAddress.adminArea1.isNullOrBlank() || shippingAddress.adminArea1 == "NV") { - actions.reject() - } else { - actions.approve() - } - } - - override fun onPayPalNativeShippingMethodChange( - actions: PayPalNativePaysheetActions, - shippingMethod: PayPalNativeShippingMethod - ) { - - viewModelScope.launch(exceptionHandler) { - orderId?.also { - try { - updateOrderUseCase(it, shippingMethod) - actions.approve() - } catch (e: IOException) { - actions.reject() - throw e - } - } - } - } - } - - private val internalState = - MutableLiveData(NativeCheckoutViewState.Initial) - val state: LiveData = internalState - - lateinit var payPalClient: PayPalNativeCheckoutClient - - private var clientId = "" - - private val exceptionHandler = CoroutineExceptionHandler { _, e -> - internalState.postValue(NativeCheckoutViewState.CheckoutError(message = e.message)) - } - - fun fetchClientId() { - internalState.postValue(NativeCheckoutViewState.FetchingClientId) - viewModelScope.launch(exceptionHandler) { - clientId = getClientIdUseCase() - initPayPalClient() - internalState.postValue(NativeCheckoutViewState.ClientIdFetched(clientId)) - } - } - - fun orderIdCheckout(shippingPreferenceType: ShippingPreferenceType, orderIntent: OrderIntent) { - internalState.postValue(NativeCheckoutViewState.CheckoutInit) - viewModelScope.launch(exceptionHandler) { - orderId = getOrderIdUseCase(shippingPreferenceType, orderIntent) - orderId?.also { - payPalClient.startCheckout(PayPalNativeCheckoutRequest(it)) - } - } - } - - fun reset() { - clientId = "" - internalState.postValue(NativeCheckoutViewState.Initial) - } - - private fun initPayPalClient() { - payPalClient = PayPalNativeCheckoutClient( - getApplication(), - CoreConfig(clientId), - "${BuildConfig.APPLICATION_ID}://paypalpay" - ) - payPalClient.listener = payPalListener - payPalClient.shippingListener = shippingListener - } - - fun captureOrder(orderId: String) = viewModelScope.launch { - // TODO: capture client metadata ID - val order = completeOrderUseCase(orderId, OrderIntent.CAPTURE, "") - internalState.postValue(NativeCheckoutViewState.OrderCaptured(order)) - } - - fun authorizeOrder(orderId: String) = viewModelScope.launch { - // TODO: capture client metadata ID - val order = completeOrderUseCase(orderId, OrderIntent.AUTHORIZE, "") - internalState.postValue(NativeCheckoutViewState.OrderAuthorized(order)) - } -} diff --git a/Demo/src/main/res/layout/fragment_pay_pal_native.xml b/Demo/src/main/res/layout/fragment_pay_pal_native.xml deleted file mode 100644 index 657166aaf..000000000 --- a/Demo/src/main/res/layout/fragment_pay_pal_native.xml +++ /dev/null @@ -1,158 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Demo/src/main/res/navigation/nav_graph.xml b/Demo/src/main/res/navigation/nav_graph.xml index 40c56c9da..ab1df523e 100644 --- a/Demo/src/main/res/navigation/nav_graph.xml +++ b/Demo/src/main/res/navigation/nav_graph.xml @@ -8,14 +8,6 @@ android:id="@+id/paymentMethodsFragment" android:name="com.paypal.android.ui.features.FeaturesFragment" android:label="@string/feature_title"> - - - @@ -28,6 +20,9 @@ + - - - - - - Checkout Cancelled User cancelled the checkout + Shipping Preference + Get From File + No Shipping + Set Provided Address + SCA ALWAYS WHEN REQUIRED @@ -41,18 +46,4 @@ Intent AUTHORIZE CAPTURE - - - Get Client ID - Try Again - Fetching Client ID… - Approved - Error - Cancelled - Client ID Fetched - Initializing Checkout… - Starting PayPal… - Oops! Something went wrong - Order Created - Checkout cancelled by user