diff --git a/WooCommerce/src/main/AndroidManifest.xml b/WooCommerce/src/main/AndroidManifest.xml index 366945a42b1..aece3b5a748 100644 --- a/WooCommerce/src/main/AndroidManifest.xml +++ b/WooCommerce/src/main/AndroidManifest.xml @@ -163,6 +163,9 @@ + { + parentFragmentManager.setFragmentResult( + WooPosCardReaderActivity.WOO_POS_CARD_CONNECTION_REQUEST_KEY, + Bundle(), + ) + } is CardReaderConnectEvent.ShowToast -> ToastUtils.showToast(requireContext(), getString(event.message)) is CardReaderConnectEvent.ShowToastString -> diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/connect/CardReaderConnectEvent.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/connect/CardReaderConnectEvent.kt index 67ac825a809..87de39f23d7 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/connect/CardReaderConnectEvent.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/connect/CardReaderConnectEvent.kt @@ -46,4 +46,6 @@ sealed class CardReaderConnectEvent : MultiLiveEvent.Event() { data class OpenWPComWebView(val url: String) : CardReaderConnectEvent() data class OpenGenericWebView(val url: String) : CardReaderConnectEvent() + + data object PopBackStackForWooPOS : CardReaderConnectEvent() } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/connect/CardReaderConnectViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/connect/CardReaderConnectViewModel.kt index f3ae59ab1e9..4a15d46862c 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/connect/CardReaderConnectViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/connect/CardReaderConnectViewModel.kt @@ -35,6 +35,7 @@ import com.woocommerce.android.ui.payments.cardreader.connect.CardReaderConnectE import com.woocommerce.android.ui.payments.cardreader.connect.CardReaderConnectEvent.OpenLocationSettings import com.woocommerce.android.ui.payments.cardreader.connect.CardReaderConnectEvent.OpenPermissionsSettings import com.woocommerce.android.ui.payments.cardreader.connect.CardReaderConnectEvent.OpenWPComWebView +import com.woocommerce.android.ui.payments.cardreader.connect.CardReaderConnectEvent.PopBackStackForWooPOS import com.woocommerce.android.ui.payments.cardreader.connect.CardReaderConnectEvent.RequestBluetoothRuntimePermissions import com.woocommerce.android.ui.payments.cardreader.connect.CardReaderConnectEvent.RequestEnableBluetooth import com.woocommerce.android.ui.payments.cardreader.connect.CardReaderConnectEvent.RequestLocationPermissions @@ -57,6 +58,7 @@ import com.woocommerce.android.ui.payments.cardreader.connect.CardReaderConnectV import com.woocommerce.android.ui.payments.cardreader.connect.CardReaderConnectViewState.MissingMerchantAddressError import com.woocommerce.android.ui.payments.cardreader.connect.CardReaderConnectViewState.MultipleExternalReadersFoundState import com.woocommerce.android.ui.payments.cardreader.connect.CardReaderConnectViewState.ScanningFailedState +import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderFlowParam import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderOnboardingChecker import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderType.BUILT_IN import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderType.EXTERNAL @@ -281,6 +283,7 @@ class CardReaderConnectViewModel @Inject constructor( Unit } } + CardReaderStatus.Connecting -> { connectionStarted = true viewState.value = provideConnectingState() @@ -309,13 +312,16 @@ class CardReaderConnectViewModel @Inject constructor( viewState.value = provideScanningState() } } + is ReadersFound -> { tracker.trackReadersDiscovered(discoveryEvent.list.size) onReadersFound(discoveryEvent) } + Succeeded -> { // noop } + is Failed -> { tracker.trackReaderDiscoveryFailed(discoveryEvent.msg) WooLog.e(WooLog.T.CARD_READER, "Scanning failed: ${discoveryEvent.msg}") @@ -330,6 +336,7 @@ class CardReaderConnectViewModel @Inject constructor( triggerEvent(ShowToast(R.string.card_reader_detail_connected_update_failed)) exitFlow(connected = false) } + CardReaderUpdateViewModel.UpdateResult.SUCCESS -> { // noop } @@ -400,6 +407,7 @@ class CardReaderConnectViewModel @Inject constructor( ::onCancelClicked, ::onLearnMoreClicked ) + EXTERNAL -> ExternalReaderScanningState( ::onCancelClicked, ::onLearnMoreClicked @@ -424,6 +432,7 @@ class CardReaderConnectViewModel @Inject constructor( tracker.trackFetchingLocationSucceeded() cardReaderManager.startConnectionToReader(cardReader, result.locationId) } + is CardReaderLocationRepository.LocationIdFetchingResult.Error -> { handleLocationFetchingError(result) } @@ -453,10 +462,12 @@ class CardReaderConnectViewModel @Inject constructor( } ) } + is CardReaderLocationRepository.LocationIdFetchingResult.Error.InvalidPostalCode -> { trackLocationFailureFetching("Invalid Postal Code") viewState.value = InvalidMerchantAddressPostCodeError(::restartFlow) } + is CardReaderLocationRepository.LocationIdFetchingResult.Error.Other -> { trackLocationFailureFetching(result.error) onReaderConnectionFailed() @@ -510,7 +521,13 @@ class CardReaderConnectViewModel @Inject constructor( private fun exitFlow(connected: Boolean) { if (!connected) { - triggerEvent(ExitWithResult(false)) + when (arguments.cardReaderFlowParam) { + is CardReaderFlowParam.CardReadersHub, + is CardReaderFlowParam.PaymentOrRefund.Payment, + is CardReaderFlowParam.PaymentOrRefund.Refund -> triggerEvent(ExitWithResult(false)) + + CardReaderFlowParam.WooPosConnection -> triggerEvent(PopBackStackForWooPOS) + } } else { triggerEvent(ShowCardReaderTutorial(arguments.cardReaderFlowParam, arguments.cardReaderType)) } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/onboarding/CardReaderOnboardingFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/onboarding/CardReaderOnboardingFragment.kt index 2d511e2167f..2251864a406 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/onboarding/CardReaderOnboardingFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/onboarding/CardReaderOnboardingFragment.kt @@ -413,6 +413,9 @@ sealed class CardReaderFlowParam : Parcelable { } } + @Parcelize + data object WooPosConnection : CardReaderFlowParam() + sealed class PaymentOrRefund : CardReaderFlowParam() { abstract val orderId: Long @@ -426,6 +429,7 @@ sealed class CardReaderFlowParam : Parcelable { ORDER, ORDER_CREATION, TRY_TAP_TO_PAY, + WOO_POS, } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/onboarding/CardReaderOnboardingViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/onboarding/CardReaderOnboardingViewModel.kt index 079e5a5740a..cc141c638a5 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/onboarding/CardReaderOnboardingViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/onboarding/CardReaderOnboardingViewModel.kt @@ -438,6 +438,9 @@ class CardReaderOnboardingViewModel @Inject constructor( CardReaderOnboardingEvent.ContinueToConnection(params, requireNotNull(arguments.cardReaderType)) ) } + is CardReaderFlowParam.WooPosConnection -> { + error("Unsupported flow param: $params") + } } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/statuschecker/CardReaderStatusCheckerViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/statuschecker/CardReaderStatusCheckerViewModel.kt index 68d0f50228a..936ef06809b 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/statuschecker/CardReaderStatusCheckerViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/statuschecker/CardReaderStatusCheckerViewModel.kt @@ -69,6 +69,7 @@ class CardReaderStatusCheckerViewModel handleOnboardingStatus(param) } } + is CardReaderFlowParam.WooPosConnection -> handleOnboardingStatus(param) } } @@ -87,6 +88,7 @@ class CardReaderStatusCheckerViewModel triggerEvent(StatusCheckerEvent.NavigateToWelcome(param, arguments.cardReaderType)) } } + else -> triggerEvent( StatusCheckerEvent.NavigateToOnboarding( CardReaderOnboardingParams.Failed(param, state), diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/tutorial/CardReaderTutorialDialogFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/tutorial/CardReaderTutorialDialogFragment.kt index 64001b78fc5..a133fe5aada 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/tutorial/CardReaderTutorialDialogFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/tutorial/CardReaderTutorialDialogFragment.kt @@ -15,6 +15,7 @@ import com.woocommerce.android.extensions.navigateSafely import com.woocommerce.android.ui.payments.PaymentsBaseDialogFragment import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderFlowParam import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderType.BUILT_IN +import com.woocommerce.android.ui.woopos.cardreader.WooPosCardReaderActivity import dagger.hilt.android.AndroidEntryPoint import javax.inject.Inject @@ -59,6 +60,12 @@ class CardReaderTutorialDialogFragment : PaymentsBaseDialogFragment(R.layout.car private fun navigateNext() { when (val param = args.cardReaderFlowParam) { is CardReaderFlowParam.CardReadersHub -> findNavController().popBackStack() + is CardReaderFlowParam.WooPosConnection -> { + parentFragmentManager.setFragmentResult( + WooPosCardReaderActivity.WOO_POS_CARD_CONNECTION_REQUEST_KEY, + Bundle(), + ) + } is CardReaderFlowParam.PaymentOrRefund -> { val action = CardReaderTutorialDialogFragmentDirections .actionCardReaderTutorialDialogFragmentToCardReaderPaymentDialogFragment(param, args.cardReaderType) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/hub/PaymentsHubViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/hub/PaymentsHubViewModel.kt index 75bf2bbdc4b..2d8d4598a30 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/hub/PaymentsHubViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/hub/PaymentsHubViewModel.kt @@ -507,6 +507,7 @@ class PaymentsHubViewModel @Inject constructor( is PaymentOrRefund -> { // no-op } + is CardReaderFlowParam.WooPosConnection -> error("Unsupported card reader flow param $params") } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/methodselection/SelectPaymentMethodEvent.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/methodselection/SelectPaymentMethodEvent.kt index a984704284b..2de1cc5b53b 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/methodselection/SelectPaymentMethodEvent.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/methodselection/SelectPaymentMethodEvent.kt @@ -36,6 +36,8 @@ data class NavigateToOrderDetails( val orderId: Long ) : MultiLiveEvent.Event() +object ReturnResultToWooPos : MultiLiveEvent.Event() + data class NavigateToTapToPaySummary( val order: Order ) : MultiLiveEvent.Event() diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/methodselection/SelectPaymentMethodFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/methodselection/SelectPaymentMethodFragment.kt index 9d301e498e0..bfe9fb3ed06 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/methodselection/SelectPaymentMethodFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/methodselection/SelectPaymentMethodFragment.kt @@ -34,6 +34,8 @@ import com.woocommerce.android.ui.payments.methodselection.SelectPaymentMethodVi import com.woocommerce.android.ui.payments.methodselection.SelectPaymentMethodViewState.Success import com.woocommerce.android.ui.payments.scantopay.ScanToPayDialogFragment import com.woocommerce.android.ui.payments.taptopay.summary.TapToPaySummaryFragment +import com.woocommerce.android.ui.woopos.cardreader.WooPosCardReaderActivity +import com.woocommerce.android.ui.woopos.cardreader.WooPosCardReaderPaymentResult import com.woocommerce.android.util.ChromeCustomTabUtils import com.woocommerce.android.util.UiHelpers import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.ShowDialog @@ -61,8 +63,12 @@ class SelectPaymentMethodFragment : BaseFragment(R.layout.fragment_select_paymen savedInstanceState: Bundle? ): View { _binding = FragmentSelectPaymentMethodBinding.inflate(inflater, container, false) - setupToolbar() - return binding.root + return if (viewModel.displayUi) { + setupToolbar() + binding.root + } else { + View(requireContext()) + } } private fun setupToolbar() { @@ -274,6 +280,18 @@ class SelectPaymentMethodFragment : BaseFragment(R.layout.fragment_select_paymen ) ) } + + is ReturnResultToWooPos -> { + parentFragmentManager.setFragmentResult( + WooPosCardReaderActivity.WOO_POS_CARD_PAYMENT_REQUEST_KEY, + Bundle().apply { + putParcelable( + WooPosCardReaderActivity.WOO_POS_CARD_PAYMENT_RESULT_KEY, + WooPosCardReaderPaymentResult.Success, + ) + } + ) + } } } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/methodselection/SelectPaymentMethodViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/methodselection/SelectPaymentMethodViewModel.kt index 37503aad91f..3a5ff92f5e1 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/methodselection/SelectPaymentMethodViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/methodselection/SelectPaymentMethodViewModel.kt @@ -27,7 +27,9 @@ import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderFlowP import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderFlowParam.PaymentOrRefund.Payment.PaymentType.ORDER_CREATION import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderFlowParam.PaymentOrRefund.Payment.PaymentType.SIMPLE import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderFlowParam.PaymentOrRefund.Payment.PaymentType.TRY_TAP_TO_PAY +import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderFlowParam.PaymentOrRefund.Payment.PaymentType.WOO_POS import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderFlowParam.PaymentOrRefund.Refund +import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderFlowParam.WooPosConnection import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderType.BUILT_IN import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderType.EXTERNAL import com.woocommerce.android.ui.payments.cardreader.payment.CardReaderPaymentCollectibilityChecker @@ -86,6 +88,9 @@ class SelectPaymentMethodViewModel @Inject constructor( private val cardReaderPaymentFlowParam get() = navArgs.cardReaderFlowParam as Payment + val displayUi: Boolean + get() = !isWooPOSPaymentFlow() + init { checkStatus() } @@ -97,7 +102,6 @@ class SelectPaymentMethodViewModel @Inject constructor( when (param) { is Payment -> { launch { - // stay on screen cardReaderTrackingInfoKeeper.setCountry( wooCommerceStore.getStoreCountryCode(selectedSite.get()) ) @@ -109,7 +113,10 @@ class SelectPaymentMethodViewModel @Inject constructor( cardReaderTrackingInfoKeeper.setCurrency(order.currency) } - showPaymentState() + when (param.paymentType) { + SIMPLE, ORDER, ORDER_CREATION, TRY_TAP_TO_PAY -> showPaymentState() + WOO_POS -> onBtReaderClicked() + } } Unit } @@ -117,6 +124,8 @@ class SelectPaymentMethodViewModel @Inject constructor( is Refund -> triggerEvent(NavigateToCardReaderRefundFlow(param, EXTERNAL)) } } + + is WooPosConnection -> error("Unsupported card reader flow param: $param") } } @@ -244,6 +253,7 @@ class SelectPaymentMethodViewModel @Inject constructor( val messageIdForPaymentType = when (cardReaderPaymentFlowParam.paymentType) { SIMPLE, TRY_TAP_TO_PAY -> R.string.simple_payments_cash_dlg_message ORDER, ORDER_CREATION -> R.string.existing_order_cash_dlg_message + WOO_POS -> error("Unsupported card reader flow param: $cardReaderPaymentFlowParam") } triggerEvent( MultiLiveEvent.Event.ShowDialog( @@ -443,6 +453,7 @@ class SelectPaymentMethodViewModel @Inject constructor( SIMPLE -> NavigateBackToHub(CardReadersHub()) TRY_TAP_TO_PAY -> NavigateToTapToPaySummary(order.first()) ORDER, ORDER_CREATION -> NavigateBackToOrderList(order.first()) + WOO_POS -> ReturnResultToWooPos } ) } @@ -452,7 +463,8 @@ class SelectPaymentMethodViewModel @Inject constructor( SIMPLE -> AnalyticsTracker.VALUE_SIMPLE_PAYMENTS_FLOW ORDER -> AnalyticsTracker.VALUE_ORDER_PAYMENTS_FLOW TRY_TAP_TO_PAY -> AnalyticsTracker.VALUE_TTP_TRY_PAYMENT_FLOW - Payment.PaymentType.ORDER_CREATION -> AnalyticsTracker.VALUE_ORDER_CREATION_PAYMENTS_FLOW + ORDER_CREATION -> AnalyticsTracker.VALUE_ORDER_CREATION_PAYMENTS_FLOW + WOO_POS -> AnalyticsTracker.VALUE_WOO_POS_PAYMENTS_FLOW } private fun onLearnMoreIppClicked() { @@ -471,6 +483,10 @@ class SelectPaymentMethodViewModel @Inject constructor( return currencyFormatter.formatCurrency(total, currencyCode) } + private fun isWooPOSPaymentFlow() = with(navArgs.cardReaderFlowParam) { + this is Payment && paymentType == WOO_POS + } + companion object { private const val DELAY_MS = 1L const val UTM_CAMPAIGN = "feature_announcement_card" diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderActivity.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderActivity.kt new file mode 100644 index 00000000000..9555e130e85 --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderActivity.kt @@ -0,0 +1,108 @@ +package com.woocommerce.android.ui.woopos.cardreader + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import androidx.activity.viewModels +import androidx.appcompat.app.AppCompatActivity +import androidx.navigation.fragment.NavHostFragment +import com.woocommerce.android.R +import com.woocommerce.android.extensions.parcelable +import com.woocommerce.android.ui.payments.cardreader.statuschecker.CardReaderStatusCheckerDialogFragmentArgs +import com.woocommerce.android.ui.payments.methodselection.SelectPaymentMethodFragmentArgs +import dagger.hilt.android.AndroidEntryPoint + +@AndroidEntryPoint +class WooPosCardReaderActivity : AppCompatActivity(R.layout.activity_woo_pos_card_reader) { + val viewModel: WooPosCardReaderViewModel by viewModels() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val navHostFragment = supportFragmentManager.findFragmentById( + R.id.woopos_card_reader_nav_host_fragment + ) as NavHostFragment + + observeEvents(navHostFragment) + observeResult(navHostFragment) + } + + private fun observeResult(navHostFragment: NavHostFragment) { + navHostFragment.childFragmentManager.setFragmentResultListener( + WOO_POS_CARD_PAYMENT_REQUEST_KEY, + this + ) { requestKey, bundle -> + when (requestKey) { + WOO_POS_CARD_PAYMENT_REQUEST_KEY -> { + val result = bundle.parcelable(WOO_POS_CARD_PAYMENT_RESULT_KEY) + setResult( + RESULT_OK, + Intent().apply { putExtra(WOO_POS_CARD_PAYMENT_RESULT_KEY, result) } + ) + finish() + } + + else -> error("Unknown request key: $requestKey") + } + } + + navHostFragment.childFragmentManager.setFragmentResultListener( + WOO_POS_CARD_CONNECTION_REQUEST_KEY, + this + ) { requestKey, _ -> + when (requestKey) { + WOO_POS_CARD_CONNECTION_REQUEST_KEY -> { + finish() + } + + else -> error("Unknown request key: $requestKey") + } + } + } + + private fun observeEvents(navHostFragment: NavHostFragment) { + viewModel.event.observe(this) { event -> + when (event) { + is WooPosCardReaderEvent.Connection -> { + val navController = navHostFragment.navController + val graph = navController.navInflater.inflate(R.navigation.nav_graph_payment_flow).apply { + setStartDestination(R.id.cardReaderStatusCheckerDialogFragment) + } + navController.setGraph( + graph, + CardReaderStatusCheckerDialogFragmentArgs( + cardReaderFlowParam = event.cardReaderFlowParam, + cardReaderType = event.cardReaderType, + ).toBundle() + ) + } + + is WooPosCardReaderEvent.Payment -> { + val navController = navHostFragment.navController + val graph = navController.navInflater.inflate(R.navigation.nav_graph_payment_flow) + navController.setGraph( + graph, + SelectPaymentMethodFragmentArgs(cardReaderFlowParam = event.cardReaderFlowParam).toBundle() + ) + } + } + } + } + + companion object { + const val WOO_POS_CARD_PAYMENT_REQUEST_KEY = "woo_pos_card_payment_request" + const val WOO_POS_CARD_CONNECTION_REQUEST_KEY = "woo_pos_card_connection_request" + const val WOO_POS_CARD_PAYMENT_RESULT_KEY = "woo_pos_card_payment_result" + internal const val WOO_POS_CARD_READER_MODE_KEY = "card_reader_connection_mode" + + fun buildIntentForCardReaderConnection(context: Context) = + Intent(context, WooPosCardReaderActivity::class.java).apply { + putExtra(WOO_POS_CARD_READER_MODE_KEY, WooPosCardReaderMode.Connection) + } + + fun buildIntentForPayment(context: Context, orderId: Long) = + Intent(context, WooPosCardReaderActivity::class.java).apply { + putExtra(WOO_POS_CARD_READER_MODE_KEY, WooPosCardReaderMode.Payment(orderId)) + } + } +} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderEvent.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderEvent.kt new file mode 100644 index 00000000000..d6cc0704332 --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderEvent.kt @@ -0,0 +1,23 @@ +package com.woocommerce.android.ui.woopos.cardreader + +import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderFlowParam +import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderType +import com.woocommerce.android.viewmodel.MultiLiveEvent + +sealed class WooPosCardReaderEvent( + val cardReaderFlowParam: CardReaderFlowParam, + val cardReaderType: CardReaderType +) : MultiLiveEvent.Event() { + data object Connection : WooPosCardReaderEvent( + cardReaderFlowParam = CardReaderFlowParam.WooPosConnection, + cardReaderType = CardReaderType.EXTERNAL + ) + + data class Payment(val orderId: Long) : WooPosCardReaderEvent( + cardReaderFlowParam = CardReaderFlowParam.PaymentOrRefund.Payment( + orderId = orderId, + paymentType = CardReaderFlowParam.PaymentOrRefund.Payment.PaymentType.WOO_POS + ), + cardReaderType = CardReaderType.EXTERNAL + ) +} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderFacade.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderFacade.kt new file mode 100644 index 00000000000..09851f52ddd --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderFacade.kt @@ -0,0 +1,53 @@ +package com.woocommerce.android.ui.woopos.cardreader + +import android.content.Intent +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.LifecycleOwner +import com.woocommerce.android.cardreader.CardReaderManager +import com.woocommerce.android.cardreader.connection.CardReaderStatus +import com.woocommerce.android.util.parcelable +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.suspendCancellableCoroutine +import javax.inject.Inject +import kotlin.coroutines.Continuation +import kotlin.coroutines.resume + +class WooPosCardReaderFacade @Inject constructor(cardReaderManager: CardReaderManager) : DefaultLifecycleObserver { + private var paymentContinuation: Continuation? = null + private var paymentResultLauncher: ActivityResultLauncher? = null + private var activity: AppCompatActivity? = null + + val readerStatus: Flow = cardReaderManager.readerStatus + + override fun onCreate(owner: LifecycleOwner) { + activity = owner as AppCompatActivity + paymentResultLauncher = activity!!.registerForActivityResult( + ActivityResultContracts.StartActivityForResult() + ) { result -> + val paymentResult = result.data!!.parcelable( + WooPosCardReaderActivity.WOO_POS_CARD_PAYMENT_RESULT_KEY + ) + paymentContinuation?.resume(paymentResult!!) + } + } + + override fun onDestroy(owner: LifecycleOwner) { + activity = null + paymentContinuation = null + paymentResultLauncher = null + } + + fun connectToReader() { + activity!!.startActivity(WooPosCardReaderActivity.buildIntentForCardReaderConnection(activity!!)) + } + + suspend fun collectPayment(orderId: Long): WooPosCardReaderPaymentResult { + return suspendCancellableCoroutine { continuation -> + paymentContinuation = continuation + paymentResultLauncher!!.launch(WooPosCardReaderActivity.buildIntentForPayment(activity!!, orderId)) + } + } +} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderMode.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderMode.kt new file mode 100644 index 00000000000..7f57fb1a874 --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderMode.kt @@ -0,0 +1,10 @@ +package com.woocommerce.android.ui.woopos.cardreader + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +@Parcelize +internal sealed class WooPosCardReaderMode : Parcelable { + data object Connection : WooPosCardReaderMode() + data class Payment(val orderId: Long) : WooPosCardReaderMode() +} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderPaymentResult.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderPaymentResult.kt new file mode 100644 index 00000000000..1266ad8766a --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderPaymentResult.kt @@ -0,0 +1,10 @@ +package com.woocommerce.android.ui.woopos.cardreader + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +@Parcelize +sealed class WooPosCardReaderPaymentResult : Parcelable { + data object Success : WooPosCardReaderPaymentResult() + data object Failure : WooPosCardReaderPaymentResult() +} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderViewModel.kt new file mode 100644 index 00000000000..adcfa5692e8 --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderViewModel.kt @@ -0,0 +1,70 @@ +package com.woocommerce.android.ui.woopos.cardreader + +import android.util.Log +import androidx.lifecycle.SavedStateHandle +import com.woocommerce.android.R +import com.woocommerce.android.cardreader.config.CardReaderConfigForSupportedCountry +import com.woocommerce.android.model.Order +import com.woocommerce.android.tools.SelectedSite +import com.woocommerce.android.ui.orders.creation.OrderCreateEditRepository +import com.woocommerce.android.ui.payments.cardreader.CardReaderCountryConfigProvider +import com.woocommerce.android.ui.woopos.cardreader.WooPosCardReaderActivity.Companion.WOO_POS_CARD_READER_MODE_KEY +import com.woocommerce.android.viewmodel.ResourceProvider +import com.woocommerce.android.viewmodel.ScopedViewModel +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.launch +import org.wordpress.android.fluxc.store.WooCommerceStore +import javax.inject.Inject + +@HiltViewModel +class WooPosCardReaderViewModel @Inject constructor( + private val orderCreateEditRepository: OrderCreateEditRepository, + private val resourceProvider: ResourceProvider, + private val cardReaderCountryConfigProvider: CardReaderCountryConfigProvider, + private val wooStore: WooCommerceStore, + private val selectedSite: SelectedSite, + savedStateHandle: SavedStateHandle, +) : ScopedViewModel(savedStateHandle) { + init { + when (val mode = savedStateHandle.get(WOO_POS_CARD_READER_MODE_KEY)) { + is WooPosCardReaderMode.Connection -> { + triggerEvent(WooPosCardReaderEvent.Connection) + } + + is WooPosCardReaderMode.Payment -> { + if (mode.orderId != -1L) { + triggerEvent(WooPosCardReaderEvent.Payment(mode.orderId)) + } else { + launch { + createTestOrder( + onSuccess = { order -> + triggerEvent(WooPosCardReaderEvent.Payment(order.id)) + }, + onFailure = { + Log.e("WooPosCardReaderViewModel", "Failed to create test order") + } + ) + } + } + } + + null -> error("WooPosCardReaderMode not found in savedStateHandle") + } + } + + private suspend fun createTestOrder(onSuccess: (Order) -> Unit, onFailure: () -> Unit) { + val countryConfig = cardReaderCountryConfigProvider.provideCountryConfigFor( + wooStore.getStoreCountryCode(selectedSite.get()) + ) as CardReaderConfigForSupportedCountry + + val result = orderCreateEditRepository.createSimplePaymentOrder( + countryConfig.minimumAllowedChargeAmount, + customerNote = resourceProvider.getString(R.string.card_reader_tap_to_pay_test_payment_note), + isTaxable = false, + ) + result.fold( + onSuccess = { onSuccess(it) }, + onFailure = { onFailure() } + ) + } +} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cart/WooPosCartNavigation.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cart/WooPosCartNavigation.kt index 9eb461c75a6..d46fcf2cb3d 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cart/WooPosCartNavigation.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cart/WooPosCartNavigation.kt @@ -7,14 +7,18 @@ import androidx.navigation.compose.composable internal const val CART_ROUTE = "cart" internal fun NavGraphBuilder.cartScreen( - onCheckoutClick: () -> Unit + onCheckoutClick: () -> Unit, + onConnectToCardReaderClicked: () -> Unit, + onCollectPaymentWithCardReader: () -> Unit, ) { composable(CART_ROUTE) { val viewModel: WooPosCartViewModel = hiltViewModel() WooPosCartScreen( viewModel = viewModel, - onCheckoutClick = onCheckoutClick + onCheckoutClick = onCheckoutClick, + onConnectToCardReaderClicked = onConnectToCardReaderClicked, + onCollectPaymentWithCardReader = onCollectPaymentWithCardReader, ) } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cart/WooPosCartScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cart/WooPosCartScreen.kt index 7080802376b..a2438aeb834 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cart/WooPosCartScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cart/WooPosCartScreen.kt @@ -2,13 +2,17 @@ package com.woocommerce.android.ui.woopos.cart import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding import androidx.compose.material.Button import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp import com.woocommerce.android.ui.woopos.util.WooPosPreview @Composable @@ -16,17 +20,27 @@ import com.woocommerce.android.ui.woopos.util.WooPosPreview fun WooPosCartScreen( viewModel: WooPosCartViewModel, onCheckoutClick: () -> Unit, + onConnectToCardReaderClicked: () -> Unit, + onCollectPaymentWithCardReader: () -> Unit, ) { WooPosCartScreen( - onButtonClicked = onCheckoutClick + onCheckoutClick = onCheckoutClick, + onConnectToCardReaderClicked = onConnectToCardReaderClicked, + onCollectPaymentWithCardReader = onCollectPaymentWithCardReader, ) } @Composable -private fun WooPosCartScreen(onButtonClicked: () -> Unit) { +private fun WooPosCartScreen( + onCheckoutClick: () -> Unit, + onConnectToCardReaderClicked: () -> Unit, + onCollectPaymentWithCardReader: () -> Unit, +) { Box( - Modifier.fillMaxSize(), - contentAlignment = Alignment.Center + Modifier + .fillMaxSize() + .padding(32.dp), + contentAlignment = Alignment.CenterStart ) { Column { Text( @@ -34,9 +48,21 @@ private fun WooPosCartScreen(onButtonClicked: () -> Unit) { style = MaterialTheme.typography.h3, color = MaterialTheme.colors.primary, ) - Button(onClick = onButtonClicked) { + Button(onClick = onCheckoutClick) { Text("Checkout") } + + Spacer(modifier = Modifier.height(16.dp)) + + Button(onClick = onConnectToCardReaderClicked) { + Text("Connect to Card Reader") + } + + Spacer(modifier = Modifier.height(16.dp)) + + Button(onClick = onCollectPaymentWithCardReader) { + Text("Collect Payment with Card Reader") + } } } } @@ -44,5 +70,9 @@ private fun WooPosCartScreen(onButtonClicked: () -> Unit) { @Composable @WooPosPreview fun WooPosCartScreenPreview() { - WooPosCartScreen(onButtonClicked = {}) + WooPosCartScreen( + onCheckoutClick = {}, + onConnectToCardReaderClicked = {}, + onCollectPaymentWithCardReader = {}, + ) } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/root/WooPosActivity.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/root/WooPosActivity.kt index f6bc0edbbcd..ef722732397 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/root/WooPosActivity.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/root/WooPosActivity.kt @@ -2,21 +2,48 @@ package com.woocommerce.android.ui.woopos.root import android.content.pm.ActivityInfo import android.os.Bundle +import android.widget.Toast import androidx.activity.compose.setContent import androidx.appcompat.app.AppCompatActivity import androidx.compose.material.MaterialTheme +import androidx.lifecycle.lifecycleScope +import com.woocommerce.android.ui.woopos.cardreader.WooPosCardReaderFacade import com.woocommerce.android.ui.woopos.root.navigation.WooPosRootHost import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.launch +import javax.inject.Inject @AndroidEntryPoint class WooPosActivity : AppCompatActivity() { + @Inject + lateinit var wooPosCardReaderFacade: WooPosCardReaderFacade + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE + lifecycle.addObserver(wooPosCardReaderFacade) + setContent { MaterialTheme { - WooPosRootHost() + WooPosRootHost( + connectToCardReader = { + lifecycleScope.launch { + wooPosCardReaderFacade.connectToReader() + } + lifecycleScope.launch { + wooPosCardReaderFacade.readerStatus.collect { + Toast.makeText(this@WooPosActivity, "Reader status: $it", Toast.LENGTH_SHORT).show() + } + } + }, + collectPaymentWithCardReader = { + lifecycleScope.launch { + val result = wooPosCardReaderFacade.collectPayment(-1) + Toast.makeText(this@WooPosActivity, "Payment result: $result", Toast.LENGTH_SHORT).show() + } + } + ) } } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/root/navigation/WooPosMainFlowGraph.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/root/navigation/WooPosMainFlowGraph.kt index cd600a29857..aa1b881ca7e 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/root/navigation/WooPosMainFlowGraph.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/root/navigation/WooPosMainFlowGraph.kt @@ -1,5 +1,6 @@ package com.woocommerce.android.ui.woopos.root.navigation +import android.content.Context import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.compose.navigation @@ -11,14 +12,18 @@ import com.woocommerce.android.ui.woopos.checkout.navigateToCheckoutScreen const val MAIN_GRAPH_ROUTE = "main-graph" fun NavGraphBuilder.checkoutGraph( - navController: NavController + navController: NavController, + onConnectToCardReaderClicked: (Context) -> Unit, + collectPaymentWithCardReader: (Context) -> Unit, ) { navigation( startDestination = CART_ROUTE, route = MAIN_GRAPH_ROUTE, ) { cartScreen( - onCheckoutClick = navController::navigateToCheckoutScreen + onCheckoutClick = navController::navigateToCheckoutScreen, + onConnectToCardReaderClicked = { onConnectToCardReaderClicked(navController.context) }, + onCollectPaymentWithCardReader = { collectPaymentWithCardReader(navController.context) }, ) checkoutScreen( onBackClick = navController::popBackStack diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/root/navigation/WooPosRootHost.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/root/navigation/WooPosRootHost.kt index 0709b338c4d..b578d21d479 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/root/navigation/WooPosRootHost.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/root/navigation/WooPosRootHost.kt @@ -1,11 +1,15 @@ package com.woocommerce.android.ui.woopos.root.navigation +import android.content.Context import androidx.compose.runtime.Composable import androidx.navigation.compose.NavHost import androidx.navigation.compose.rememberNavController @Composable -fun WooPosRootHost() { +fun WooPosRootHost( + connectToCardReader: (context: Context) -> Unit, + collectPaymentWithCardReader: (context: Context) -> Unit, +) { val rootController = rememberNavController() NavHost( @@ -18,6 +22,8 @@ fun WooPosRootHost() { ) { checkoutGraph( navController = rootController, + onConnectToCardReaderClicked = { context -> connectToCardReader(context) }, + collectPaymentWithCardReader = { context -> collectPaymentWithCardReader(context) }, ) } } diff --git a/WooCommerce/src/main/res/layout/activity_woo_pos_card_reader.xml b/WooCommerce/src/main/res/layout/activity_woo_pos_card_reader.xml new file mode 100644 index 00000000000..c7dad055558 --- /dev/null +++ b/WooCommerce/src/main/res/layout/activity_woo_pos_card_reader.xml @@ -0,0 +1,7 @@ + diff --git a/WooCommerce/src/main/res/values/themes.xml b/WooCommerce/src/main/res/values/themes.xml index ecad50d3eb6..98bff8b1056 100644 --- a/WooCommerce/src/main/res/values/themes.xml +++ b/WooCommerce/src/main/res/values/themes.xml @@ -154,4 +154,13 @@