diff --git a/example/src/main/kotlin/com/processout/example/ui/screen/nativeapm/NativeApmFragment.kt b/example/src/main/kotlin/com/processout/example/ui/screen/nativeapm/NativeApmFragment.kt index b93a9c829..2f9105990 100644 --- a/example/src/main/kotlin/com/processout/example/ui/screen/nativeapm/NativeApmFragment.kt +++ b/example/src/main/kotlin/com/processout/example/ui/screen/nativeapm/NativeApmFragment.kt @@ -17,6 +17,7 @@ import com.processout.sdk.core.onFailure import com.processout.sdk.core.onSuccess import com.processout.sdk.ui.napm.PONativeAlternativePaymentConfiguration import com.processout.sdk.ui.napm.PONativeAlternativePaymentConfiguration.Button +import com.processout.sdk.ui.napm.PONativeAlternativePaymentDelegate import com.processout.sdk.ui.napm.PONativeAlternativePaymentLauncher import com.processout.sdk.ui.nativeapm.PONativeAlternativePaymentMethodConfiguration import com.processout.sdk.ui.nativeapm.PONativeAlternativePaymentMethodLauncher @@ -48,7 +49,10 @@ class NativeApmFragment : BaseFragment( showAlert(result.toMessage()) } } - launcherCompose = PONativeAlternativePaymentLauncher.create(from = this) { result -> + launcherCompose = PONativeAlternativePaymentLauncher.create( + from = this, + delegate = object : PONativeAlternativePaymentDelegate {} + ) { result -> viewModel.reset() result.onSuccess { showAlert(getString(R.string.success)) diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/checkout/DynamicCheckoutActivity.kt b/ui/src/main/kotlin/com/processout/sdk/ui/checkout/DynamicCheckoutActivity.kt index f828a7a4b..ede5b87cc 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/checkout/DynamicCheckoutActivity.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/checkout/DynamicCheckoutActivity.kt @@ -23,7 +23,6 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.google.android.gms.wallet.Wallet.WalletOptions import com.processout.sdk.R import com.processout.sdk.api.ProcessOut -import com.processout.sdk.api.dispatcher.napm.PODefaultNativeAlternativePaymentMethodEventDispatcher import com.processout.sdk.api.model.event.POSavedPaymentMethodsEvent import com.processout.sdk.api.model.event.POSavedPaymentMethodsEvent.DidDeleteCustomerToken import com.processout.sdk.api.model.request.POInvoiceRequest @@ -73,20 +72,18 @@ internal class DynamicCheckoutActivity : BaseTransparentPortraitActivity() { legacyEventDispatcher = null ) } - val nativeAlternativePaymentEventDispatcher = PODefaultNativeAlternativePaymentMethodEventDispatcher() val nativeAlternativePayment: NativeAlternativePaymentViewModel by viewModels { NativeAlternativePaymentViewModel.Factory( app = application, configuration = nativeAlternativePaymentConfiguration(), - eventDispatcher = nativeAlternativePaymentEventDispatcher + legacyEventDispatcher = null ) } DynamicCheckoutViewModel.Factory( app = application, configuration = configuration, cardTokenization = cardTokenization, - nativeAlternativePayment = nativeAlternativePayment, - nativeAlternativePaymentEventDispatcher = nativeAlternativePaymentEventDispatcher + nativeAlternativePayment = nativeAlternativePayment ) } diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/checkout/DynamicCheckoutInteractor.kt b/ui/src/main/kotlin/com/processout/sdk/ui/checkout/DynamicCheckoutInteractor.kt index e8440fb7e..53c315336 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/checkout/DynamicCheckoutInteractor.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/checkout/DynamicCheckoutInteractor.kt @@ -11,9 +11,9 @@ import coil.request.ImageRequest import coil.request.ImageResult import com.processout.sdk.R import com.processout.sdk.api.dispatcher.POEventDispatcher -import com.processout.sdk.api.dispatcher.napm.PODefaultNativeAlternativePaymentMethodEventDispatcher import com.processout.sdk.api.model.event.PODynamicCheckoutEvent import com.processout.sdk.api.model.event.PODynamicCheckoutEvent.* +import com.processout.sdk.api.model.event.PONativeAlternativePaymentMethodEvent import com.processout.sdk.api.model.event.PONativeAlternativePaymentMethodEvent.WillSubmitParameters import com.processout.sdk.api.model.request.* import com.processout.sdk.api.model.response.* @@ -75,7 +75,6 @@ internal class DynamicCheckoutInteractor( private val googlePayService: POGooglePayService, private val cardTokenization: CardTokenizationViewModel, private val nativeAlternativePayment: NativeAlternativePaymentViewModel, - private val nativeAlternativePaymentEventDispatcher: PODefaultNativeAlternativePaymentMethodEventDispatcher, private val eventDispatcher: POEventDispatcher = POEventDispatcher, private var logAttributes: Map = logAttributes( invoiceId = configuration.invoiceRequest.invoiceId @@ -123,12 +122,11 @@ internal class DynamicCheckoutInteractor( private suspend fun start() { handleCompletions() - dispatchEvents() dispatchSideEffects() + collectEvents() collectInvoice() collectInvoiceAuthorizationRequest() collectTokenizedCard() - collectDefaultValues() collectSavedPaymentMethodsConfiguration() fetchConfiguration() } @@ -995,14 +993,7 @@ internal class DynamicCheckoutInteractor( } } - private fun dispatch(event: PODynamicCheckoutEvent) { - interactorScope.launch { - eventDispatcher.send(event) - POLogger.debug("Event has been sent: %s", event) - } - } - - private fun dispatchEvents() { + private fun collectEvents() { eventDispatcher.subscribeForRequest( coroutineScope = interactorScope ) { request -> @@ -1010,18 +1001,19 @@ internal class DynamicCheckoutInteractor( eventDispatcher.send(request.toResponse(shouldContinue = false)) } } - interactorScope.launch { - nativeAlternativePaymentEventDispatcher.events.collect { event -> - if (event is WillSubmitParameters) { - _state.update { it.copy(processingPaymentMethod = _state.value.selectedPaymentMethod) } - } - eventDispatcher.send(event) + eventDispatcher.subscribe( + coroutineScope = interactorScope + ) { event -> + if (event is WillSubmitParameters) { + _state.update { it.copy(processingPaymentMethod = _state.value.selectedPaymentMethod) } } } + } + + private fun dispatch(event: PODynamicCheckoutEvent) { interactorScope.launch { - nativeAlternativePaymentEventDispatcher.defaultValuesRequest.collect { request -> - eventDispatcher.send(request) - } + eventDispatcher.send(event) + POLogger.debug("Event has been sent: %s", event) } } @@ -1081,16 +1073,6 @@ internal class DynamicCheckoutInteractor( POLogger.debug("Deleted local customer token: %s", tokenId) } - private fun collectDefaultValues() { - eventDispatcher.subscribeForResponse( - coroutineScope = interactorScope - ) { response -> - interactorScope.launch { - nativeAlternativePaymentEventDispatcher.provideDefaultValues(response) - } - } - } - private fun cancel() { val failure = ProcessOutResult.Failure( code = Cancelled, diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/checkout/DynamicCheckoutViewModel.kt b/ui/src/main/kotlin/com/processout/sdk/ui/checkout/DynamicCheckoutViewModel.kt index d3044b64d..91ecab1de 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/checkout/DynamicCheckoutViewModel.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/checkout/DynamicCheckoutViewModel.kt @@ -7,7 +7,6 @@ import androidx.lifecycle.viewModelScope import com.google.android.gms.wallet.Wallet.WalletOptions import com.processout.sdk.R import com.processout.sdk.api.ProcessOut -import com.processout.sdk.api.dispatcher.napm.PODefaultNativeAlternativePaymentMethodEventDispatcher import com.processout.sdk.api.model.response.PODynamicCheckoutPaymentMethod.Display import com.processout.sdk.api.service.googlepay.PODefaultGooglePayService import com.processout.sdk.ui.card.tokenization.CardTokenizationViewModel @@ -44,8 +43,7 @@ internal class DynamicCheckoutViewModel private constructor( private val app: Application, private val configuration: PODynamicCheckoutConfiguration, private val cardTokenization: CardTokenizationViewModel, - private val nativeAlternativePayment: NativeAlternativePaymentViewModel, - private val nativeAlternativePaymentEventDispatcher: PODefaultNativeAlternativePaymentMethodEventDispatcher + private val nativeAlternativePayment: NativeAlternativePaymentViewModel ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun create(modelClass: Class): T = @@ -63,8 +61,7 @@ internal class DynamicCheckoutViewModel private constructor( .build() ), cardTokenization = cardTokenization, - nativeAlternativePayment = nativeAlternativePayment, - nativeAlternativePaymentEventDispatcher = nativeAlternativePaymentEventDispatcher + nativeAlternativePayment = nativeAlternativePayment ) ) as T } diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentBottomSheet.kt b/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentBottomSheet.kt index efacd14ed..5204e8846 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentBottomSheet.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentBottomSheet.kt @@ -56,7 +56,7 @@ internal class NativeAlternativePaymentBottomSheet : BaseBottomSheetDialogFragme gatewayConfigurationId = String(), submitButton = Button() ), - eventDispatcher = PODefaultEventDispatchers.defaultNativeAlternativePaymentMethod + legacyEventDispatcher = PODefaultEventDispatchers.defaultNativeAlternativePaymentMethod ) } diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentInteractor.kt b/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentInteractor.kt index d55797bce..a9e228157 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentInteractor.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentInteractor.kt @@ -16,6 +16,7 @@ import coil.request.CachePolicy import coil.request.ImageRequest import coil.request.ImageResult import com.processout.sdk.R +import com.processout.sdk.api.dispatcher.POEventDispatcher import com.processout.sdk.api.dispatcher.napm.PODefaultNativeAlternativePaymentMethodEventDispatcher import com.processout.sdk.api.model.event.PONativeAlternativePaymentMethodEvent import com.processout.sdk.api.model.event.PONativeAlternativePaymentMethodEvent.* @@ -63,7 +64,8 @@ internal class NativeAlternativePaymentInteractor( private val barcodeBitmapProvider: BarcodeBitmapProvider, private val mediaStorageProvider: MediaStorageProvider, private val captureRetryStrategy: PORetryStrategy, - private val eventDispatcher: PODefaultNativeAlternativePaymentMethodEventDispatcher, + private val legacyEventDispatcher: PODefaultNativeAlternativePaymentMethodEventDispatcher?, // TODO: remove before next major release. + private val eventDispatcher: POEventDispatcher = POEventDispatcher, private var logAttributes: Map = logAttributes( invoiceId = configuration.invoiceId, gatewayConfigurationId = configuration.gatewayConfigurationId @@ -260,21 +262,14 @@ internal class NativeAlternativePaymentInteractor( fields = fields, focusedFieldId = focusedFieldId ) - val isLoading = _state.value is Loading - if (eventDispatcher.subscribedForDefaultValuesRequest()) { - _state.update { - if (isLoading) { - Loaded(updatedStateValue) - } else { - Submitted(updatedStateValue) - } + _state.update { + if (_state.value is Loading) { + Loaded(updatedStateValue) + } else { + Submitted(updatedStateValue) } - requestDefaultValues(parameters) - } else if (isLoading) { - startUserInput(updatedStateValue) - } else { - continueUserInput(updatedStateValue) } + requestDefaultValues(parameters) } private fun failWithUnknownInputParameter( @@ -368,24 +363,39 @@ internal class NativeAlternativePaymentInteractor( parameters = parameters ) latestDefaultValuesRequest = request - eventDispatcher.send(request) + if (legacyEventDispatcher?.subscribedForDefaultValuesRequest() == true) { + legacyEventDispatcher.send(request) + } else { + eventDispatcher.send(request) + } POLogger.debug("Requested to provide default values for payment parameters: %s", request) } } private fun collectDefaultValues() { interactorScope.launch { - eventDispatcher.defaultValuesResponse.collect { response -> - if (response.uuid == latestDefaultValuesRequest?.uuid) { - latestDefaultValuesRequest = null - POLogger.debug("Collected default values for payment parameters: %s", response) - _state.whenLoaded { stateValue -> - startUserInput(stateValue.updateFieldValues(response.defaultValues)) - } - _state.whenSubmitted { stateValue -> - continueUserInput(stateValue.updateFieldValues(response.defaultValues)) - } - } + legacyEventDispatcher?.defaultValuesResponse?.collect { response -> + handleDefaultValues(response) + } + } + eventDispatcher.subscribeForResponse( + coroutineScope = interactorScope + ) { response -> + handleDefaultValues(response) + } + } + + private fun handleDefaultValues( + response: PONativeAlternativePaymentMethodDefaultValuesResponse + ) { + if (response.uuid == latestDefaultValuesRequest?.uuid) { + latestDefaultValuesRequest = null + POLogger.debug("Collected default values for payment parameters: %s", response) + _state.whenLoaded { stateValue -> + startUserInput(stateValue.updateFieldValues(response.defaultValues)) + } + _state.whenSubmitted { stateValue -> + continueUserInput(stateValue.updateFieldValues(response.defaultValues)) } } } @@ -922,6 +932,7 @@ internal class NativeAlternativePaymentInteractor( private fun dispatch(event: PONativeAlternativePaymentMethodEvent) { interactorScope.launch { + legacyEventDispatcher?.send(event) eventDispatcher.send(event) POLogger.debug("Event has been sent: %s", event) } diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentViewModel.kt b/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentViewModel.kt index 67ad1d222..fa6c7a387 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentViewModel.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentViewModel.kt @@ -42,7 +42,7 @@ internal class NativeAlternativePaymentViewModel private constructor( class Factory( private val app: Application, private val configuration: PONativeAlternativePaymentConfiguration, - private val eventDispatcher: PODefaultNativeAlternativePaymentMethodEventDispatcher + private val legacyEventDispatcher: PODefaultNativeAlternativePaymentMethodEventDispatcher? ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun create(modelClass: Class): T = @@ -62,7 +62,7 @@ internal class NativeAlternativePaymentViewModel private constructor( maxDelay = 90 * 1000, factor = 1.45 ), - eventDispatcher = eventDispatcher + legacyEventDispatcher = legacyEventDispatcher ) ) as T } diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/napm/PONativeAlternativePaymentDelegate.kt b/ui/src/main/kotlin/com/processout/sdk/ui/napm/PONativeAlternativePaymentDelegate.kt new file mode 100644 index 000000000..aa60e4ba0 --- /dev/null +++ b/ui/src/main/kotlin/com/processout/sdk/ui/napm/PONativeAlternativePaymentDelegate.kt @@ -0,0 +1,25 @@ +package com.processout.sdk.ui.napm + +import com.processout.sdk.api.model.event.PONativeAlternativePaymentMethodEvent +import com.processout.sdk.api.model.response.PONativeAlternativePaymentMethodParameter + +/** + * Delegate that allows to handle events during native alternative payments. + */ +interface PONativeAlternativePaymentDelegate { + + /** + * Invoked on native alternative payment lifecycle events. + */ + fun onEvent(event: PONativeAlternativePaymentMethodEvent) {} + + /** + * Allows to prefill default values for the given parameters during native alternative payment. + * Return a map where key is a [PONativeAlternativePaymentMethodParameter.key] and value is a custom default value. + * It's not mandatory to provide default values for all parameters. + */ + suspend fun defaultValues( + gatewayConfigurationId: String, + parameters: List + ): Map = emptyMap() +} diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/napm/PONativeAlternativePaymentLauncher.kt b/ui/src/main/kotlin/com/processout/sdk/ui/napm/PONativeAlternativePaymentLauncher.kt index 8ba003437..1d8004a2c 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/napm/PONativeAlternativePaymentLauncher.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/napm/PONativeAlternativePaymentLauncher.kt @@ -5,16 +5,26 @@ import androidx.activity.ComponentActivity import androidx.activity.result.ActivityResultLauncher import androidx.core.app.ActivityOptionsCompat import androidx.fragment.app.Fragment +import androidx.lifecycle.lifecycleScope import com.processout.sdk.R +import com.processout.sdk.api.dispatcher.POEventDispatcher +import com.processout.sdk.api.model.event.PONativeAlternativePaymentMethodEvent +import com.processout.sdk.api.model.request.PONativeAlternativePaymentMethodDefaultValuesRequest +import com.processout.sdk.api.model.response.toResponse import com.processout.sdk.core.POUnit import com.processout.sdk.core.ProcessOutActivityResult +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch /** * Launcher that starts [NativeAlternativePaymentActivity] and provides the result. */ class PONativeAlternativePaymentLauncher private constructor( + private val scope: CoroutineScope, private val launcher: ActivityResultLauncher, - private val activityOptions: ActivityOptionsCompat + private val activityOptions: ActivityOptionsCompat, + private val delegate: PONativeAlternativePaymentDelegate, + private val eventDispatcher: POEventDispatcher = POEventDispatcher ) { companion object { @@ -24,13 +34,34 @@ class PONativeAlternativePaymentLauncher private constructor( */ fun create( from: Fragment, + delegate: PONativeAlternativePaymentDelegate, callback: (ProcessOutActivityResult) -> Unit ) = PONativeAlternativePaymentLauncher( + scope = from.lifecycleScope, launcher = from.registerForActivityResult( NativeAlternativePaymentActivityContract(), callback ), - activityOptions = createActivityOptions(from.requireContext()) + activityOptions = createActivityOptions(from.requireContext()), + delegate = delegate + ) + + /** + * Creates the launcher from Fragment. + * __Note:__ Required to call in _onCreate()_ to register for activity result. + */ + @Deprecated(message = "Use alternative function.") + fun create( + from: Fragment, + callback: (ProcessOutActivityResult) -> Unit + ) = PONativeAlternativePaymentLauncher( + scope = from.lifecycleScope, + launcher = from.registerForActivityResult( + NativeAlternativePaymentActivityContract(), + callback + ), + activityOptions = createActivityOptions(from.requireContext()), + delegate = object : PONativeAlternativePaymentDelegate {} ) /** @@ -39,14 +70,36 @@ class PONativeAlternativePaymentLauncher private constructor( */ fun create( from: ComponentActivity, + delegate: PONativeAlternativePaymentDelegate, callback: (ProcessOutActivityResult) -> Unit ) = PONativeAlternativePaymentLauncher( + scope = from.lifecycleScope, launcher = from.registerForActivityResult( NativeAlternativePaymentActivityContract(), from.activityResultRegistry, callback ), - activityOptions = createActivityOptions(from) + activityOptions = createActivityOptions(from), + delegate = delegate + ) + + /** + * Creates the launcher from Activity. + * __Note:__ Required to call in _onCreate()_ to register for activity result. + */ + @Deprecated(message = "Use alternative function.") + fun create( + from: ComponentActivity, + callback: (ProcessOutActivityResult) -> Unit + ) = PONativeAlternativePaymentLauncher( + scope = from.lifecycleScope, + launcher = from.registerForActivityResult( + NativeAlternativePaymentActivityContract(), + from.activityResultRegistry, + callback + ), + activityOptions = createActivityOptions(from), + delegate = object : PONativeAlternativePaymentDelegate {} ) private fun createActivityOptions(context: Context) = @@ -55,6 +108,31 @@ class PONativeAlternativePaymentLauncher private constructor( ) } + init { + dispatchEvents() + dispatchDefaultValues() + } + + private fun dispatchEvents() { + eventDispatcher.subscribe( + coroutineScope = scope + ) { delegate.onEvent(it) } + } + + private fun dispatchDefaultValues() { + eventDispatcher.subscribeForRequest( + coroutineScope = scope + ) { request -> + scope.launch { + val defaultValues = delegate.defaultValues( + gatewayConfigurationId = request.gatewayConfigurationId, + parameters = request.parameters + ) + eventDispatcher.send(request.toResponse(defaultValues)) + } + } + } + /** * Launches the activity. */