From ee4cf7b24d93eba09e9c235617008265605c2d93 Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Mon, 25 Nov 2024 15:18:58 +0200 Subject: [PATCH 01/16] Update POProxy3DSServiceRequest (remove random UUID) and POProxy3DSServiceResponse (add Close) --- .../api/service/proxy3ds/POProxy3DSServiceRequest.kt | 10 ++++------ .../api/service/proxy3ds/POProxy3DSServiceResponse.kt | 2 ++ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/sdk/src/main/kotlin/com/processout/sdk/api/service/proxy3ds/POProxy3DSServiceRequest.kt b/sdk/src/main/kotlin/com/processout/sdk/api/service/proxy3ds/POProxy3DSServiceRequest.kt index 72db3a61d..5ab96f2b5 100644 --- a/sdk/src/main/kotlin/com/processout/sdk/api/service/proxy3ds/POProxy3DSServiceRequest.kt +++ b/sdk/src/main/kotlin/com/processout/sdk/api/service/proxy3ds/POProxy3DSServiceRequest.kt @@ -12,21 +12,19 @@ import java.util.UUID sealed interface POProxy3DSServiceRequest : POEventDispatcher.Request { data class Authentication( - override val uuid: UUID = UUID.randomUUID(), + override val uuid: UUID, val configuration: PO3DS2Configuration ) : POProxy3DSServiceRequest data class Challenge( - override val uuid: UUID = UUID.randomUUID(), + override val uuid: UUID, val challenge: PO3DS2Challenge ) : POProxy3DSServiceRequest data class Redirect( - override val uuid: UUID = UUID.randomUUID(), + override val uuid: UUID, val redirect: PO3DSRedirect ) : POProxy3DSServiceRequest - data class Cleanup( - override val uuid: UUID = UUID.randomUUID() - ) : POProxy3DSServiceRequest + data class Cleanup(override val uuid: UUID) : POProxy3DSServiceRequest } diff --git a/sdk/src/main/kotlin/com/processout/sdk/api/service/proxy3ds/POProxy3DSServiceResponse.kt b/sdk/src/main/kotlin/com/processout/sdk/api/service/proxy3ds/POProxy3DSServiceResponse.kt index da68c8487..3214edb00 100644 --- a/sdk/src/main/kotlin/com/processout/sdk/api/service/proxy3ds/POProxy3DSServiceResponse.kt +++ b/sdk/src/main/kotlin/com/processout/sdk/api/service/proxy3ds/POProxy3DSServiceResponse.kt @@ -24,4 +24,6 @@ sealed interface POProxy3DSServiceResponse : POEventDispatcher.Response { override val uuid: UUID, val result: ProcessOutResult ) : POProxy3DSServiceResponse + + data class Close(override val uuid: UUID) : POProxy3DSServiceResponse } From 762b28ba56bf98d4fb5b915f8bc9a9feda3f5d14 Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Mon, 25 Nov 2024 15:42:21 +0200 Subject: [PATCH 02/16] PODynamicCheckoutLauncher: send Close after cleanup, improve 3ds events dispatching --- .../ui/checkout/PODynamicCheckoutLauncher.kt | 45 ++++++++++--------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/checkout/PODynamicCheckoutLauncher.kt b/ui/src/main/kotlin/com/processout/sdk/ui/checkout/PODynamicCheckoutLauncher.kt index 4b2a6f48f..38c87420e 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/checkout/PODynamicCheckoutLauncher.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/checkout/PODynamicCheckoutLauncher.kt @@ -164,40 +164,41 @@ class PODynamicCheckoutLauncher private constructor( ) { request -> when (request) { is Authentication -> threeDSService.authenticationRequest(request.configuration) { - scope.launch { - eventDispatcher.send( - POProxy3DSServiceResponse.Authentication( - uuid = request.uuid, - result = it - ) + dispatch( + POProxy3DSServiceResponse.Authentication( + uuid = request.uuid, + result = it ) - } + ) } is Challenge -> threeDSService.handle(request.challenge) { - scope.launch { - eventDispatcher.send( - POProxy3DSServiceResponse.Challenge( - uuid = request.uuid, - result = it - ) + dispatch( + POProxy3DSServiceResponse.Challenge( + uuid = request.uuid, + result = it ) - } + ) } is Redirect -> threeDSService.handle(request.redirect) { - scope.launch { - eventDispatcher.send( - POProxy3DSServiceResponse.Redirect( - uuid = request.uuid, - result = it - ) + dispatch( + POProxy3DSServiceResponse.Redirect( + uuid = request.uuid, + result = it ) - } + ) + } + is Cleanup -> { + threeDSService.cleanup() + dispatch(POProxy3DSServiceResponse.Close(uuid = request.uuid)) } - is Cleanup -> threeDSService.cleanup() } } } + private fun dispatch(response: POEventDispatcher.Response) { + scope.launch { eventDispatcher.send(response) } + } + /** * Launches the activity. */ From dbec2a068480a08fac5456d1b7a52de2ac4eab76 Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Mon, 25 Nov 2024 16:07:32 +0200 Subject: [PATCH 03/16] Type fix --- .../com/processout/sdk/ui/checkout/PODynamicCheckoutLauncher.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/checkout/PODynamicCheckoutLauncher.kt b/ui/src/main/kotlin/com/processout/sdk/ui/checkout/PODynamicCheckoutLauncher.kt index 38c87420e..c8f89e7e3 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/checkout/PODynamicCheckoutLauncher.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/checkout/PODynamicCheckoutLauncher.kt @@ -195,7 +195,7 @@ class PODynamicCheckoutLauncher private constructor( } } - private fun dispatch(response: POEventDispatcher.Response) { + private fun dispatch(response: POProxy3DSServiceResponse) { scope.launch { eventDispatcher.send(response) } } From bf0df9826dc80d7d82e3391bc6ebbea3eac2d96f Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Mon, 25 Nov 2024 16:16:48 +0200 Subject: [PATCH 04/16] PODefaultProxy3DSService: use UUID for all events and handle Close --- .../proxy3ds/PODefaultProxy3DSService.kt | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/sdk/src/main/kotlin/com/processout/sdk/api/service/proxy3ds/PODefaultProxy3DSService.kt b/sdk/src/main/kotlin/com/processout/sdk/api/service/proxy3ds/PODefaultProxy3DSService.kt index 57b7f4067..c371c24a9 100644 --- a/sdk/src/main/kotlin/com/processout/sdk/api/service/proxy3ds/PODefaultProxy3DSService.kt +++ b/sdk/src/main/kotlin/com/processout/sdk/api/service/proxy3ds/PODefaultProxy3DSService.kt @@ -12,14 +12,17 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.MainScope import kotlinx.coroutines.cancel import kotlinx.coroutines.launch +import java.util.UUID /** @suppress */ @ProcessOutInternalApi class PODefaultProxy3DSService( - private val scope: CoroutineScope = MainScope(), - private val eventDispatcher: POEventDispatcher = POEventDispatcher + private val scope: CoroutineScope = MainScope() ) : POProxy3DSService { + private val uuid = UUID.randomUUID() + private val eventDispatcher = POEventDispatcher + private var authenticationCallback: ((ProcessOutResult) -> Unit)? = null private var challengeCallback: ((ProcessOutResult) -> Unit)? = null private var redirectCallback: ((ProcessOutResult) -> Unit)? = null @@ -28,10 +31,14 @@ class PODefaultProxy3DSService( eventDispatcher.subscribeForResponse( coroutineScope = scope ) { response -> + if (response.uuid != uuid) { + return@subscribeForResponse + } when (response) { is POProxy3DSServiceResponse.Authentication -> authenticationCallback?.invoke(response.result) is POProxy3DSServiceResponse.Challenge -> challengeCallback?.invoke(response.result) is POProxy3DSServiceResponse.Redirect -> redirectCallback?.invoke(response.result) + is POProxy3DSServiceResponse.Close -> close() } } } @@ -41,9 +48,7 @@ class PODefaultProxy3DSService( callback: (ProcessOutResult) -> Unit ) { authenticationCallback = callback - scope.launch { - eventDispatcher.send(Authentication(configuration = configuration)) - } + dispatch(Authentication(uuid = uuid, configuration = configuration)) } override fun handle( @@ -51,9 +56,7 @@ class PODefaultProxy3DSService( callback: (ProcessOutResult) -> Unit ) { challengeCallback = callback - scope.launch { - eventDispatcher.send(Challenge(challenge = challenge)) - } + dispatch(Challenge(uuid = uuid, challenge = challenge)) } override fun handle( @@ -61,18 +64,18 @@ class PODefaultProxy3DSService( callback: (ProcessOutResult) -> Unit ) { redirectCallback = callback - scope.launch { - eventDispatcher.send(Redirect(redirect = redirect)) - } + dispatch(Redirect(uuid = uuid, redirect = redirect)) } override fun cleanup() { authenticationCallback = null challengeCallback = null redirectCallback = null - scope.launch { - eventDispatcher.send(Cleanup()) - } + dispatch(Cleanup(uuid = uuid)) + } + + private fun dispatch(request: POProxy3DSServiceRequest) { + scope.launch { eventDispatcher.send(request) } } override fun close() { From d6213e38b034f5916936472e84193e810a0aa04f Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Mon, 25 Nov 2024 17:20:59 +0200 Subject: [PATCH 05/16] Provide new 3ds service instance on each invoice authorization and handle result with checking invoice id and processing payment method type --- .../ui/checkout/DynamicCheckoutInteractor.kt | 38 ++++++++++++------- .../ui/checkout/DynamicCheckoutViewModel.kt | 2 - 2 files changed, 25 insertions(+), 15 deletions(-) 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 4e1a099d4..20e45d422 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 @@ -30,7 +30,7 @@ import com.processout.sdk.api.service.googlepay.POGooglePayConfiguration.Payment import com.processout.sdk.api.service.googlepay.POGooglePayConfiguration.PaymentDataConfiguration.TransactionInfo import com.processout.sdk.api.service.googlepay.POGooglePayRequestBuilder import com.processout.sdk.api.service.googlepay.POGooglePayService -import com.processout.sdk.api.service.proxy3ds.POProxy3DSService +import com.processout.sdk.api.service.proxy3ds.PODefaultProxy3DSService import com.processout.sdk.core.POFailure.Code.* import com.processout.sdk.core.ProcessOutResult import com.processout.sdk.core.logger.POLogAttribute @@ -74,7 +74,6 @@ internal class DynamicCheckoutInteractor( private val app: Application, private var configuration: PODynamicCheckoutConfiguration, private val invoicesService: POInvoicesService, - private val threeDSService: POProxy3DSService, private val googlePayService: POGooglePayService, private val cardTokenization: CardTokenizationViewModel, private val cardTokenizationEventDispatcher: PODefaultCardTokenizationEventDispatcher, @@ -131,7 +130,6 @@ internal class DynamicCheckoutInteractor( dispatchEvents() collectInvoice() collectInvoiceAuthorizationRequest() - collectAuthorizeInvoiceResult() collectTokenizedCard() collectPreferredScheme() collectDefaultValues() @@ -812,26 +810,41 @@ internal class DynamicCheckoutInteractor( eventDispatcher.subscribeForResponse( coroutineScope = interactorScope ) { response -> + @Suppress("DEPRECATION") invoicesService.authorizeInvoice( request = response.request, - threeDSService = threeDSService - ) + threeDSService = PODefaultProxy3DSService() + ) { result -> + handleInvoiceAuthorization( + state = _state.value, + invoiceId = response.request.invoiceId, + result = result + ) + } } } - private fun collectAuthorizeInvoiceResult() { - interactorScope.launch { - invoicesService.authorizeInvoiceResult.collect { result -> - when (_state.value.processingPaymentMethod) { - is Card -> cardTokenizationEventDispatcher.complete(result) - else -> result.onSuccess { + private fun handleInvoiceAuthorization( + state: DynamicCheckoutInteractorState, + invoiceId: String, + result: ProcessOutResult + ) { + if (invoiceId == state.invoice.id && state.isInvoiceValid) { + when (state.processingPaymentMethod) { + is Card -> interactorScope.launch { + cardTokenizationEventDispatcher.complete(result) + } + is GooglePay, + is AlternativePayment, + is CustomerToken -> + result.onSuccess { handleSuccess() }.onFailure { failure -> invalidateInvoice( reason = PODynamicCheckoutInvoiceInvalidationReason.Failure(failure) ) } - } + else -> {} } } } @@ -977,7 +990,6 @@ internal class DynamicCheckoutInteractor( } fun onCleared() { - threeDSService.close() handler.removeCallbacksAndMessages(null) } } 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 7e8bc5bfd..57d4ad018 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 @@ -13,7 +13,6 @@ import com.processout.sdk.api.model.response.POAlternativePaymentMethodResponse import com.processout.sdk.api.model.response.PODynamicCheckoutPaymentMethod.Display import com.processout.sdk.api.model.response.POGooglePayCardTokenizationData import com.processout.sdk.api.service.googlepay.PODefaultGooglePayService -import com.processout.sdk.api.service.proxy3ds.PODefaultProxy3DSService import com.processout.sdk.core.ProcessOutResult import com.processout.sdk.ui.card.tokenization.CardTokenizationViewModel import com.processout.sdk.ui.card.tokenization.CardTokenizationViewModelState @@ -59,7 +58,6 @@ internal class DynamicCheckoutViewModel private constructor( app = app, configuration = configuration, invoicesService = ProcessOut.instance.invoices, - threeDSService = PODefaultProxy3DSService(), googlePayService = PODefaultGooglePayService( application = app, walletOptions = WalletOptions.Builder() From 7dac3b653f500cf792e7f3400cc281625526fdeb Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Mon, 25 Nov 2024 17:35:50 +0200 Subject: [PATCH 06/16] 'isInvoiceValid = false' on restart for consistency --- .../processout/sdk/ui/checkout/DynamicCheckoutInteractor.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 20e45d422..93087d4eb 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 @@ -141,7 +141,8 @@ internal class DynamicCheckoutInteractor( logAttributes = logAttributes(invoiceId = invoiceRequest.invoiceId) reset( state = _state.value.copy( - invoice = POInvoice(id = invoiceRequest.invoiceId) + invoice = POInvoice(id = invoiceRequest.invoiceId), + isInvoiceValid = false ) ) interactorScope.launch { start() } From fabff070851a43096d24d94604e4c3992ee93bf7 Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Mon, 25 Nov 2024 20:27:51 +0200 Subject: [PATCH 07/16] Refactored GooglePay, APM and authorize invoice --- .../ui/checkout/DynamicCheckoutActivity.kt | 52 ++++++-- .../sdk/ui/checkout/DynamicCheckoutEvent.kt | 18 ++- .../ui/checkout/DynamicCheckoutInteractor.kt | 118 +++++++++++------- .../ui/checkout/DynamicCheckoutViewModel.kt | 11 -- 4 files changed, 129 insertions(+), 70 deletions(-) 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 5b8e36095..dfa715e08 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 @@ -24,6 +24,8 @@ import com.google.android.gms.wallet.WalletConstants import com.processout.sdk.api.dispatcher.card.tokenization.PODefaultCardTokenizationEventDispatcher import com.processout.sdk.api.dispatcher.napm.PODefaultNativeAlternativePaymentMethodEventDispatcher import com.processout.sdk.api.model.request.POInvoiceRequest +import com.processout.sdk.api.model.response.POAlternativePaymentMethodResponse +import com.processout.sdk.api.model.response.POGooglePayCardTokenizationData import com.processout.sdk.core.POFailure.Code.Cancelled import com.processout.sdk.core.POFailure.Code.Generic import com.processout.sdk.core.POUnit @@ -40,8 +42,7 @@ import com.processout.sdk.ui.checkout.DynamicCheckoutActivityContract.Companion. import com.processout.sdk.ui.checkout.DynamicCheckoutActivityContract.Companion.EXTRA_RESULT import com.processout.sdk.ui.checkout.DynamicCheckoutCompletion.Failure import com.processout.sdk.ui.checkout.DynamicCheckoutCompletion.Success -import com.processout.sdk.ui.checkout.DynamicCheckoutEvent.Dismiss -import com.processout.sdk.ui.checkout.DynamicCheckoutEvent.PermissionRequestResult +import com.processout.sdk.ui.checkout.DynamicCheckoutEvent.* import com.processout.sdk.ui.checkout.DynamicCheckoutSideEffect.* import com.processout.sdk.ui.checkout.PODynamicCheckoutConfiguration.CancelButton import com.processout.sdk.ui.checkout.screen.DynamicCheckoutScreen @@ -131,7 +132,10 @@ internal class DynamicCheckoutActivity : BaseTransparentPortraitActivity() { ) private lateinit var googlePayLauncher: POGooglePayCardTokenizationLauncher + private var pendingGooglePay: GooglePay? = null + private lateinit var alternativePaymentLauncher: POAlternativePaymentMethodCustomTabLauncher + private var pendingAlternativePayment: AlternativePayment? = null private val permissionsLauncher = registerForActivityResult( ActivityResultContracts.RequestMultiplePermissions(), @@ -151,11 +155,11 @@ internal class DynamicCheckoutActivity : BaseTransparentPortraitActivity() { walletOptions = WalletOptions.Builder() .setEnvironment(configuration?.googlePay?.environment?.value ?: WalletConstants.ENVIRONMENT_TEST) .build(), - callback = viewModel::handleGooglePay + callback = ::handleGooglePay ) alternativePaymentLauncher = POAlternativePaymentMethodCustomTabLauncher.create( from = this, - callback = viewModel::handleAlternativePayment + callback = ::handleAlternativePayment ) setContent { val isLightTheme = !isSystemInDarkTheme() @@ -209,15 +213,45 @@ internal class DynamicCheckoutActivity : BaseTransparentPortraitActivity() { private fun handle(sideEffect: DynamicCheckoutSideEffect) { when (sideEffect) { - is GooglePay -> googlePayLauncher.launch(sideEffect.paymentDataRequest) - is AlternativePayment -> alternativePaymentLauncher.launch( - uri = Uri.parse(sideEffect.redirectUrl), - returnUrl = sideEffect.returnUrl - ) + is GooglePay -> { + pendingGooglePay = sideEffect + googlePayLauncher.launch(sideEffect.paymentDataRequest) + } + is AlternativePayment -> { + pendingAlternativePayment = sideEffect + alternativePaymentLauncher.launch( + uri = Uri.parse(sideEffect.redirectUrl), + returnUrl = sideEffect.returnUrl + ) + } is PermissionRequest -> requestPermission(sideEffect) } } + private fun handleGooglePay(result: ProcessOutResult) { + pendingGooglePay?.let { + pendingGooglePay = null + viewModel.onEvent( + GooglePayResult( + paymentMethodId = it.paymentMethodId, + result = result + ) + ) + } + } + + private fun handleAlternativePayment(result: ProcessOutResult) { + pendingAlternativePayment?.let { + pendingAlternativePayment = null + viewModel.onEvent( + AlternativePaymentResult( + paymentMethodId = it.paymentMethodId, + result = result + ) + ) + } + } + private fun requestPermission(request: PermissionRequest) { when { ContextCompat.checkSelfPermission( diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/checkout/DynamicCheckoutEvent.kt b/ui/src/main/kotlin/com/processout/sdk/ui/checkout/DynamicCheckoutEvent.kt index 1f6414479..1fe18d8e6 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/checkout/DynamicCheckoutEvent.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/checkout/DynamicCheckoutEvent.kt @@ -1,6 +1,8 @@ package com.processout.sdk.ui.checkout import androidx.compose.ui.text.input.TextFieldValue +import com.processout.sdk.api.model.response.POAlternativePaymentMethodResponse +import com.processout.sdk.api.model.response.POGooglePayCardTokenizationData import com.processout.sdk.core.ProcessOutResult import org.json.JSONObject @@ -26,14 +28,24 @@ internal sealed interface DynamicCheckoutEvent { val paymentMethodId: String? ) : DynamicCheckoutEvent + data class ActionConfirmationRequested( + val id: String + ) : DynamicCheckoutEvent + data class DialogAction( val actionId: String, val paymentMethodId: String?, val isConfirmed: Boolean ) : DynamicCheckoutEvent - data class ActionConfirmationRequested( - val id: String + data class GooglePayResult( + val paymentMethodId: String, + val result: ProcessOutResult + ) : DynamicCheckoutEvent + + data class AlternativePaymentResult( + val paymentMethodId: String, + val result: ProcessOutResult ) : DynamicCheckoutEvent data class PermissionRequestResult( @@ -49,10 +61,12 @@ internal sealed interface DynamicCheckoutEvent { internal sealed interface DynamicCheckoutSideEffect { data class GooglePay( + val paymentMethodId: String, val paymentDataRequest: JSONObject ) : DynamicCheckoutSideEffect data class AlternativePayment( + val paymentMethodId: String, val redirectUrl: String, val returnUrl: String ) : DynamicCheckoutSideEffect 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 93087d4eb..4fa3dd0ad 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 @@ -21,7 +21,6 @@ import com.processout.sdk.api.model.response.* import com.processout.sdk.api.model.response.POBillingAddressCollectionMode.* import com.processout.sdk.api.model.response.PODynamicCheckoutPaymentMethod.* import com.processout.sdk.api.model.response.PODynamicCheckoutPaymentMethod.Flow.express -import com.processout.sdk.api.model.response.PODynamicCheckoutPaymentMethod.Unknown import com.processout.sdk.api.model.response.POTransaction.Status.* import com.processout.sdk.api.service.POInvoicesService import com.processout.sdk.api.service.googlepay.POGooglePayConfiguration @@ -31,7 +30,8 @@ import com.processout.sdk.api.service.googlepay.POGooglePayConfiguration.Payment import com.processout.sdk.api.service.googlepay.POGooglePayRequestBuilder import com.processout.sdk.api.service.googlepay.POGooglePayService import com.processout.sdk.api.service.proxy3ds.PODefaultProxy3DSService -import com.processout.sdk.core.POFailure.Code.* +import com.processout.sdk.core.POFailure.Code.Cancelled +import com.processout.sdk.core.POFailure.Code.Generic import com.processout.sdk.core.ProcessOutResult import com.processout.sdk.core.logger.POLogAttribute import com.processout.sdk.core.logger.POLogger @@ -409,8 +409,10 @@ internal class DynamicCheckoutInteractor( is FieldValueChanged -> onFieldValueChanged(event) is FieldFocusChanged -> onFieldFocusChanged(event) is Action -> onAction(event) - is DialogAction -> onDialogAction(event) is ActionConfirmationRequested -> onActionConfirmationRequested(event) + is DialogAction -> onDialogAction(event) + is GooglePayResult -> handleGooglePay(event.paymentMethodId, event.result) + is AlternativePaymentResult -> handleAlternativePayment(event.paymentMethodId, event.result) is PermissionRequestResult -> handlePermission(event) is Dismiss -> dismiss(event) } @@ -549,6 +551,13 @@ internal class DynamicCheckoutInteractor( } } + private fun onActionConfirmationRequested(event: ActionConfirmationRequested) { + POLogger.debug("Requested the user to confirm the action: %s", event.id) + if (event.id == ActionId.CANCEL) { + dispatch(DidRequestCancelConfirmation) + } + } + private fun onDialogAction(event: DialogAction) { val paymentMethod = event.paymentMethodId?.let { paymentMethod(it) } when (paymentMethod) { @@ -562,13 +571,6 @@ internal class DynamicCheckoutInteractor( } } - private fun onActionConfirmationRequested(event: ActionConfirmationRequested) { - POLogger.debug("Requested the user to confirm the action: %s", event.id) - if (event.id == ActionId.CANCEL) { - dispatch(DidRequestCancelConfirmation) - } - } - private fun PaymentMethod.isExpress(): Boolean = when (this) { is Card, is NativeAlternativePayment -> false @@ -616,6 +618,7 @@ internal class DynamicCheckoutInteractor( _state.update { it.copy(processingPaymentMethod = paymentMethod) } _sideEffects.send( DynamicCheckoutSideEffect.GooglePay( + paymentMethodId = paymentMethod.id, paymentDataRequest = paymentMethod.paymentDataRequest ) ) @@ -629,6 +632,7 @@ internal class DynamicCheckoutInteractor( if (shouldSavePaymentMethod) { _state.update { it.copy(processingPaymentMethod = paymentMethod) } authorizeInvoice( + paymentMethod = paymentMethod, source = paymentMethod.gatewayConfigurationId, saveSource = true, allowFallbackToSale = true, @@ -644,7 +648,8 @@ internal class DynamicCheckoutInteractor( } if (redirectUrl.isNullOrBlank()) { handleAlternativePayment( - ProcessOutResult.Failure( + paymentMethodId = paymentMethod.id, + result = ProcessOutResult.Failure( code = Generic(), message = "Missing redirect URL in alternative payment configuration." ) @@ -654,7 +659,8 @@ internal class DynamicCheckoutInteractor( val returnUrl = configuration.alternativePayment.returnUrl if (returnUrl.isNullOrBlank()) { handleAlternativePayment( - ProcessOutResult.Failure( + paymentMethodId = paymentMethod.id, + result = ProcessOutResult.Failure( code = Generic(), message = "Missing return URL in alternative payment configuration." ) @@ -665,6 +671,7 @@ internal class DynamicCheckoutInteractor( _state.update { it.copy(processingPaymentMethod = paymentMethod) } _sideEffects.send( DynamicCheckoutSideEffect.AlternativePayment( + paymentMethodId = paymentMethod.id, redirectUrl = redirectUrl, returnUrl = returnUrl ) @@ -677,30 +684,53 @@ internal class DynamicCheckoutInteractor( submitAlternativePayment(paymentMethod) } else { _state.update { it.copy(processingPaymentMethod = paymentMethod) } - authorizeInvoice(source = paymentMethod.configuration.customerTokenId) + authorizeInvoice( + paymentMethod = paymentMethod, + source = paymentMethod.configuration.customerTokenId + ) } } - fun handleGooglePay(result: ProcessOutResult) { - result.onSuccess { response -> - authorizeInvoice(source = response.card.id) - }.onFailure { failure -> - invalidateInvoice( - reason = PODynamicCheckoutInvoiceInvalidationReason.Failure(failure) - ) + private fun handleGooglePay( + paymentMethodId: String, + result: ProcessOutResult + ) { + _state.value.processingPaymentMethod?.let { paymentMethod -> + if (paymentMethod.id != paymentMethodId) { + return + } + result.onSuccess { response -> + authorizeInvoice( + paymentMethod = paymentMethod, + source = response.card.id + ) + }.onFailure { failure -> + invalidateInvoice( + reason = PODynamicCheckoutInvoiceInvalidationReason.Failure(failure) + ) + } } } - fun handleAlternativePayment(result: ProcessOutResult) { - result.onSuccess { response -> - authorizeInvoice( - source = response.gatewayToken, - allowFallbackToSale = true - ) - }.onFailure { failure -> - invalidateInvoice( - reason = PODynamicCheckoutInvoiceInvalidationReason.Failure(failure) - ) + private fun handleAlternativePayment( + paymentMethodId: String, + result: ProcessOutResult + ) { + _state.value.processingPaymentMethod?.let { paymentMethod -> + if (paymentMethod.id != paymentMethodId) { + return + } + result.onSuccess { response -> + authorizeInvoice( + paymentMethod = paymentMethod, + source = response.gatewayToken, + allowFallbackToSale = true + ) + }.onFailure { failure -> + invalidateInvoice( + reason = PODynamicCheckoutInvoiceInvalidationReason.Failure(failure) + ) + } } } @@ -764,34 +794,26 @@ internal class DynamicCheckoutInteractor( private fun collectTokenizedCard() { interactorScope.launch { cardTokenizationEventDispatcher.processTokenizedCardRequest.collect { request -> - _state.update { it.copy(processingPaymentMethod = _state.value.selectedPaymentMethod) } - authorizeInvoice( - source = request.card.id, - saveSource = request.saveCard, - clientSecret = configuration.invoiceRequest.clientSecret - ) + _state.value.selectedPaymentMethod?.let { paymentMethod -> + _state.update { it.copy(processingPaymentMethod = paymentMethod) } + authorizeInvoice( + paymentMethod = paymentMethod, + source = request.card.id, + saveSource = request.saveCard, + clientSecret = configuration.invoiceRequest.clientSecret + ) + } } } } private fun authorizeInvoice( + paymentMethod: PaymentMethod, source: String, saveSource: Boolean = false, allowFallbackToSale: Boolean = false, clientSecret: String? = null ) { - val paymentMethod = _state.value.processingPaymentMethod - if (paymentMethod == null) { - invalidateInvoice( - reason = PODynamicCheckoutInvoiceInvalidationReason.Failure( - failure = ProcessOutResult.Failure( - code = Internal(), - message = "Failed to authorize invoice: payment method is null." - ) - ) - ) - return - } interactorScope.launch { val request = PODynamicCheckoutInvoiceAuthorizationRequest( request = POInvoiceAuthorizationRequest( 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 57d4ad018..45dbdf52c 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 @@ -9,11 +9,8 @@ import com.processout.sdk.R import com.processout.sdk.api.ProcessOut import com.processout.sdk.api.dispatcher.card.tokenization.PODefaultCardTokenizationEventDispatcher import com.processout.sdk.api.dispatcher.napm.PODefaultNativeAlternativePaymentMethodEventDispatcher -import com.processout.sdk.api.model.response.POAlternativePaymentMethodResponse import com.processout.sdk.api.model.response.PODynamicCheckoutPaymentMethod.Display -import com.processout.sdk.api.model.response.POGooglePayCardTokenizationData import com.processout.sdk.api.service.googlepay.PODefaultGooglePayService -import com.processout.sdk.core.ProcessOutResult import com.processout.sdk.ui.card.tokenization.CardTokenizationViewModel import com.processout.sdk.ui.card.tokenization.CardTokenizationViewModelState import com.processout.sdk.ui.checkout.DynamicCheckoutInteractorState.Field @@ -94,14 +91,6 @@ internal class DynamicCheckoutViewModel private constructor( fun onEvent(event: DynamicCheckoutEvent) = interactor.onEvent(event) - fun handleGooglePay( - result: ProcessOutResult - ) = interactor.handleGooglePay(result) - - fun handleAlternativePayment( - result: ProcessOutResult - ) = interactor.handleAlternativePayment(result) - private fun combine( interactorState: DynamicCheckoutInteractorState, cardTokenizationState: CardTokenizationViewModelState, From 25f23979144268a1957f54be84e9b1fd190ff77e Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Tue, 26 Nov 2024 17:27:15 +0200 Subject: [PATCH 08/16] Removed 'isInvoiceValid' from interactor state and simplified related logic --- .../ui/checkout/DynamicCheckoutInteractor.kt | 55 +++++++++---------- .../DynamicCheckoutInteractorState.kt | 3 +- .../ui/checkout/DynamicCheckoutViewModel.kt | 10 ++-- 3 files changed, 32 insertions(+), 36 deletions(-) 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 4fa3dd0ad..c7099d053 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 @@ -118,8 +118,7 @@ internal class DynamicCheckoutInteractor( private fun initState() = DynamicCheckoutInteractorState( loading = true, - invoice = POInvoice(id = configuration.invoiceRequest.invoiceId), - isInvoiceValid = false, + invoice = null, paymentMethods = emptyList(), submitActionId = ActionId.SUBMIT, cancelActionId = ActionId.CANCEL @@ -139,21 +138,16 @@ internal class DynamicCheckoutInteractor( private fun restart(invoiceRequest: POInvoiceRequest) { configuration = configuration.copy(invoiceRequest = invoiceRequest) logAttributes = logAttributes(invoiceId = invoiceRequest.invoiceId) - reset( - state = _state.value.copy( - invoice = POInvoice(id = invoiceRequest.invoiceId), - isInvoiceValid = false - ) - ) + reset() interactorScope.launch { start() } } - private fun reset(state: DynamicCheckoutInteractorState) { + private fun reset() { interactorScope.coroutineContext.cancelChildren() + handler.removeCallbacksAndMessages(null) latestInvoiceRequest = null resetPaymentMethods() _completion.update { Awaiting } - _state.update { state } } private fun resetPaymentMethods() { @@ -203,7 +197,6 @@ internal class DynamicCheckoutInteractor( it.copy( loading = false, invoice = invoice, - isInvoiceValid = true, paymentMethods = mappedPaymentMethods ) } @@ -438,7 +431,7 @@ internal class DynamicCheckoutInteractor( invalidateInvoice( reason = PODynamicCheckoutInvoiceInvalidationReason.PaymentMethodChanged ) - } else if (state.isInvoiceValid) { + } else if (state.invoice != null) { start(paymentMethod) } _state.update { @@ -747,9 +740,10 @@ internal class DynamicCheckoutInteractor( errorMessage = app.getString(R.string.po_dynamic_checkout_error_generic) } } + val currentInvoice = _state.value.invoice ?: POInvoice(id = configuration.invoiceRequest.invoiceId) _state.update { it.copy( - isInvoiceValid = false, + invoice = null, selectedPaymentMethod = null, processingPaymentMethod = null, errorMessage = errorMessage @@ -757,7 +751,7 @@ internal class DynamicCheckoutInteractor( } if (latestInvoiceRequest == null) { val request = PODynamicCheckoutInvoiceRequest( - currentInvoice = _state.value.invoice, + currentInvoice = currentInvoice, invalidationReason = reason ) latestInvoiceRequest = request @@ -852,23 +846,24 @@ internal class DynamicCheckoutInteractor( invoiceId: String, result: ProcessOutResult ) { - if (invoiceId == state.invoice.id && state.isInvoiceValid) { - when (state.processingPaymentMethod) { - is Card -> interactorScope.launch { - cardTokenizationEventDispatcher.complete(result) + if (invoiceId != state.invoice?.id) { + return + } + when (state.processingPaymentMethod) { + is Card -> interactorScope.launch { + cardTokenizationEventDispatcher.complete(result) + } + is GooglePay, + is AlternativePayment, + is CustomerToken -> + result.onSuccess { + handleSuccess() + }.onFailure { failure -> + invalidateInvoice( + reason = PODynamicCheckoutInvoiceInvalidationReason.Failure(failure) + ) } - is GooglePay, - is AlternativePayment, - is CustomerToken -> - result.onSuccess { - handleSuccess() - }.onFailure { failure -> - invalidateInvoice( - reason = PODynamicCheckoutInvoiceInvalidationReason.Failure(failure) - ) - } - else -> {} - } + else -> {} } } diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/checkout/DynamicCheckoutInteractorState.kt b/ui/src/main/kotlin/com/processout/sdk/ui/checkout/DynamicCheckoutInteractorState.kt index e0e928192..9be73fb69 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/checkout/DynamicCheckoutInteractorState.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/checkout/DynamicCheckoutInteractorState.kt @@ -8,8 +8,7 @@ import org.json.JSONObject internal data class DynamicCheckoutInteractorState( val loading: Boolean, - val invoice: POInvoice, - val isInvoiceValid: Boolean, + val invoice: POInvoice?, val paymentMethods: List, val submitActionId: String, val cancelActionId: String, 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 45dbdf52c..101963354 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 @@ -239,7 +239,7 @@ internal class DynamicCheckoutViewModel private constructor( id = id, state = regularPaymentState( display = paymentMethod.display, - loading = !interactorState.isInvoiceValid, + loading = interactorState.invoice == null, selected = selected ), content = if (selected) Content.Card(cardTokenizationState) else null, @@ -251,7 +251,7 @@ internal class DynamicCheckoutViewModel private constructor( state = regularPaymentState( display = paymentMethod.display, description = app.getString(R.string.po_dynamic_checkout_warning_redirect), - loading = !interactorState.isInvoiceValid, + loading = interactorState.invoice == null, selected = selected ), content = alternativePaymentContent(paymentMethod), @@ -259,14 +259,16 @@ internal class DynamicCheckoutViewModel private constructor( id = interactorState.submitActionId, text = submitButtonText, primary = true, - loading = id == interactorState.processingPaymentMethod?.id || !interactorState.isInvoiceValid + loading = id == interactorState.processingPaymentMethod?.id || + interactorState.invoice == null ) ) else null is NativeAlternativePayment -> RegularPayment( id = id, state = regularPaymentState( display = paymentMethod.display, - loading = !interactorState.isInvoiceValid || nativeAlternativePaymentState is Loading, + loading = interactorState.invoice == null || + nativeAlternativePaymentState is Loading, selected = selected ), content = if (selected) Content.NativeAlternativePayment(nativeAlternativePaymentState) else null, From c000e82d4e04d0537cf0e0ce020ea8ae09a12cd3 Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Wed, 4 Dec 2024 12:46:23 +0200 Subject: [PATCH 09/16] Return Job from authorizeInvoice() --- .../sdk/api/service/DefaultInvoicesService.kt | 57 +++++++++---------- .../sdk/api/service/POInvoicesService.kt | 3 +- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/sdk/src/main/kotlin/com/processout/sdk/api/service/DefaultInvoicesService.kt b/sdk/src/main/kotlin/com/processout/sdk/api/service/DefaultInvoicesService.kt index 52f898bc3..eba84e6d5 100644 --- a/sdk/src/main/kotlin/com/processout/sdk/api/service/DefaultInvoicesService.kt +++ b/sdk/src/main/kotlin/com/processout/sdk/api/service/DefaultInvoicesService.kt @@ -14,6 +14,7 @@ import com.processout.sdk.core.ProcessOutResult import com.processout.sdk.core.logger.POLogAttribute import com.processout.sdk.core.logger.POLogger import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.launch @@ -79,39 +80,37 @@ internal class DefaultInvoicesService( request: POInvoiceAuthorizationRequest, threeDSService: PO3DSService, callback: (ProcessOutResult) -> Unit - ) { - scope.launch { - when (val result = repository.authorizeInvoice(request)) { - is ProcessOutResult.Success -> - result.value.customerAction?.let { action -> - this@DefaultInvoicesService.threeDSService - .handle(action, threeDSService) { serviceResult -> - @Suppress("DEPRECATION") - when (serviceResult) { - is ProcessOutResult.Success -> - authorizeInvoice( - request.copy(source = serviceResult.value), - threeDSService, - callback - ) - is ProcessOutResult.Failure -> { - threeDSService.cleanup() - callback(serviceResult) - } + ): Job = scope.launch { + when (val result = repository.authorizeInvoice(request)) { + is ProcessOutResult.Success -> + result.value.customerAction?.let { action -> + this@DefaultInvoicesService.threeDSService + .handle(action, threeDSService) { serviceResult -> + @Suppress("DEPRECATION") + when (serviceResult) { + is ProcessOutResult.Success -> + authorizeInvoice( + request.copy(source = serviceResult.value), + threeDSService, + callback + ) + is ProcessOutResult.Failure -> { + threeDSService.cleanup() + callback(serviceResult) } } - } ?: run { - threeDSService.cleanup() - callback(ProcessOutResult.Success(Unit)) - } - is ProcessOutResult.Failure -> { - POLogger.warn( - message = "Failed to authorize invoice: %s", result, - attributes = mapOf(POLogAttribute.INVOICE_ID to request.invoiceId) - ) + } + } ?: run { threeDSService.cleanup() - callback(result) + callback(ProcessOutResult.Success(Unit)) } + is ProcessOutResult.Failure -> { + POLogger.warn( + message = "Failed to authorize invoice: %s", result, + attributes = mapOf(POLogAttribute.INVOICE_ID to request.invoiceId) + ) + threeDSService.cleanup() + callback(result) } } } diff --git a/sdk/src/main/kotlin/com/processout/sdk/api/service/POInvoicesService.kt b/sdk/src/main/kotlin/com/processout/sdk/api/service/POInvoicesService.kt index 43265ffad..6fb3476e2 100644 --- a/sdk/src/main/kotlin/com/processout/sdk/api/service/POInvoicesService.kt +++ b/sdk/src/main/kotlin/com/processout/sdk/api/service/POInvoicesService.kt @@ -11,6 +11,7 @@ import com.processout.sdk.api.model.response.PONativeAlternativePaymentMethodTra import com.processout.sdk.core.ProcessOutCallback import com.processout.sdk.core.ProcessOutResult import com.processout.sdk.core.annotation.ProcessOutInternalApi +import kotlinx.coroutines.Job import kotlinx.coroutines.flow.SharedFlow /** @@ -41,7 +42,7 @@ interface POInvoicesService { request: POInvoiceAuthorizationRequest, threeDSService: PO3DSService, callback: (ProcessOutResult) -> Unit - ) + ): Job /** * Initiates native alternative payment with the given request. From 7877c3b2492dba806c645ce459c247b10f014476 Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Wed, 4 Dec 2024 17:22:59 +0200 Subject: [PATCH 10/16] POCustomTabAuthorizationActivity logs --- .../ui/web/customtab/POCustomTabAuthorizationActivity.kt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/sdk/src/main/kotlin/com/processout/sdk/ui/web/customtab/POCustomTabAuthorizationActivity.kt b/sdk/src/main/kotlin/com/processout/sdk/ui/web/customtab/POCustomTabAuthorizationActivity.kt index 3c1a172e0..d05738ae0 100644 --- a/sdk/src/main/kotlin/com/processout/sdk/ui/web/customtab/POCustomTabAuthorizationActivity.kt +++ b/sdk/src/main/kotlin/com/processout/sdk/ui/web/customtab/POCustomTabAuthorizationActivity.kt @@ -61,7 +61,10 @@ class POCustomTabAuthorizationActivity : AppCompatActivity() { ?.let { configuration = it } if (!::configuration.isInitialized) { - POLogger.warn("Configuration is not provided. Possibly started from redirect activity by a deep link when flow is already finished.") + POLogger.info( + "Configuration is not provided. Possibly started to clear the back stack and force finish. " + + "Also possibly started from the redirect activity by a deep link when flow is already finished." + ) finish() return } @@ -93,7 +96,7 @@ class POCustomTabAuthorizationActivity : AppCompatActivity() { Cancelled -> finishWithActivityResult( ProcessOutActivityResult.Failure( POFailure.Code.Cancelled, - "Cancelled by user with back press, gesture or cancel button." + "Cancelled by the user with back press, gesture or cancel action." ) ) is Timeout -> handleTimeout(uiState.clearBackStack) From d4fa535fbb16376a3da1209fb96d5e141c944d86 Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Wed, 4 Dec 2024 18:23:41 +0200 Subject: [PATCH 11/16] Added EXTRA_FORCE_FINISH to CustomTab and WebView contracts --- .../ui/web/customtab/CustomTabAuthorizationActivityContract.kt | 1 + .../sdk/ui/web/webview/WebViewAuthorizationActivityContract.kt | 1 + 2 files changed, 2 insertions(+) diff --git a/sdk/src/main/kotlin/com/processout/sdk/ui/web/customtab/CustomTabAuthorizationActivityContract.kt b/sdk/src/main/kotlin/com/processout/sdk/ui/web/customtab/CustomTabAuthorizationActivityContract.kt index 28b6b4ce0..7e0901b1e 100644 --- a/sdk/src/main/kotlin/com/processout/sdk/ui/web/customtab/CustomTabAuthorizationActivityContract.kt +++ b/sdk/src/main/kotlin/com/processout/sdk/ui/web/customtab/CustomTabAuthorizationActivityContract.kt @@ -18,6 +18,7 @@ internal class CustomTabAuthorizationActivityContract( const val EXTRA_CONFIGURATION = "${BuildConfig.LIBRARY_PACKAGE_NAME}.EXTRA_CONFIGURATION" const val EXTRA_RESULT = "${BuildConfig.LIBRARY_PACKAGE_NAME}.EXTRA_RESULT" const val EXTRA_TIMEOUT_FINISH = "${BuildConfig.LIBRARY_PACKAGE_NAME}.EXTRA_TIMEOUT_FINISH" + const val EXTRA_FORCE_FINISH = "${BuildConfig.LIBRARY_PACKAGE_NAME}.EXTRA_FORCE_FINISH" } fun startActivity(configuration: CustomTabConfiguration) { diff --git a/sdk/src/main/kotlin/com/processout/sdk/ui/web/webview/WebViewAuthorizationActivityContract.kt b/sdk/src/main/kotlin/com/processout/sdk/ui/web/webview/WebViewAuthorizationActivityContract.kt index 15c49f82d..5a9360ba3 100644 --- a/sdk/src/main/kotlin/com/processout/sdk/ui/web/webview/WebViewAuthorizationActivityContract.kt +++ b/sdk/src/main/kotlin/com/processout/sdk/ui/web/webview/WebViewAuthorizationActivityContract.kt @@ -18,6 +18,7 @@ internal class WebViewAuthorizationActivityContract( companion object { const val EXTRA_CONFIGURATION = "${BuildConfig.LIBRARY_PACKAGE_NAME}.EXTRA_CONFIGURATION" const val EXTRA_RESULT = "${BuildConfig.LIBRARY_PACKAGE_NAME}.EXTRA_RESULT" + const val EXTRA_FORCE_FINISH = "${BuildConfig.LIBRARY_PACKAGE_NAME}.EXTRA_FORCE_FINISH" } fun startActivity( From 30a24a16edb57dd97a2218cb28c6d1dc0d44d796 Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Wed, 4 Dec 2024 19:05:44 +0200 Subject: [PATCH 12/16] CustomTab, WebView contracts and related classes - open with internal annotation and rename with PO prefix --- ...ternativePaymentMethodCustomTabLauncher.kt | 20 ++++++++--------- ...OAlternativePaymentMethodWebViewBuilder.kt | 4 ++-- .../threeds/PO3DSRedirectCustomTabLauncher.kt | 22 +++++++++---------- .../ui/threeds/PO3DSRedirectWebViewBuilder.kt | 4 ++-- .../sdk/ui/web/ActivityResultApi.kt | 10 --------- .../sdk/ui/web/POActivityResultApi.kt | 13 +++++++++++ .../CustomTabAuthorizationViewModel.kt | 6 ++--- .../web/customtab/CustomTabConfiguration.kt | 14 ------------ .../POCustomTabAuthorizationActivity.kt | 14 ++++++------ ...CustomTabAuthorizationActivityContract.kt} | 11 ++++++---- .../web/customtab/POCustomTabConfiguration.kt | 17 ++++++++++++++ .../webview/POWebViewAuthorizationActivity.kt | 12 +++++----- ...POWebViewAuthorizationActivityContract.kt} | 11 ++++++---- ...iguration.kt => POWebViewConfiguration.kt} | 9 +++++--- .../sdk/ui/web/webview/ProcessOutWebView.kt | 2 +- .../WebViewAuthorizationActivityLauncher.kt | 12 +++++----- 16 files changed, 98 insertions(+), 83 deletions(-) delete mode 100644 sdk/src/main/kotlin/com/processout/sdk/ui/web/ActivityResultApi.kt create mode 100644 sdk/src/main/kotlin/com/processout/sdk/ui/web/POActivityResultApi.kt delete mode 100644 sdk/src/main/kotlin/com/processout/sdk/ui/web/customtab/CustomTabConfiguration.kt rename sdk/src/main/kotlin/com/processout/sdk/ui/web/customtab/{CustomTabAuthorizationActivityContract.kt => POCustomTabAuthorizationActivityContract.kt} (82%) create mode 100644 sdk/src/main/kotlin/com/processout/sdk/ui/web/customtab/POCustomTabConfiguration.kt rename sdk/src/main/kotlin/com/processout/sdk/ui/web/webview/{WebViewAuthorizationActivityContract.kt => POWebViewAuthorizationActivityContract.kt} (84%) rename sdk/src/main/kotlin/com/processout/sdk/ui/web/webview/{WebViewConfiguration.kt => POWebViewConfiguration.kt} (50%) diff --git a/sdk/src/main/kotlin/com/processout/sdk/ui/apm/POAlternativePaymentMethodCustomTabLauncher.kt b/sdk/src/main/kotlin/com/processout/sdk/ui/apm/POAlternativePaymentMethodCustomTabLauncher.kt index 30436c0b4..89232faa3 100644 --- a/sdk/src/main/kotlin/com/processout/sdk/ui/apm/POAlternativePaymentMethodCustomTabLauncher.kt +++ b/sdk/src/main/kotlin/com/processout/sdk/ui/apm/POAlternativePaymentMethodCustomTabLauncher.kt @@ -15,10 +15,10 @@ import com.processout.sdk.core.logger.POLogger import com.processout.sdk.ui.web.WebAuthorizationActivityResultDispatcher import com.processout.sdk.ui.web.WebAuthorizationDelegate import com.processout.sdk.ui.web.WebAuthorizationDelegateCache -import com.processout.sdk.ui.web.customtab.CustomTabAuthorizationActivityContract -import com.processout.sdk.ui.web.customtab.CustomTabConfiguration +import com.processout.sdk.ui.web.customtab.POCustomTabAuthorizationActivityContract +import com.processout.sdk.ui.web.customtab.POCustomTabConfiguration import com.processout.sdk.ui.web.webview.WebViewAuthorizationActivityLauncher -import com.processout.sdk.ui.web.webview.WebViewConfiguration +import com.processout.sdk.ui.web.webview.POWebViewConfiguration /** * Launcher that starts [POCustomTabAuthorizationActivity][com.processout.sdk.ui.web.customtab.POCustomTabAuthorizationActivity] @@ -30,7 +30,7 @@ class POAlternativePaymentMethodCustomTabLauncher private constructor( private val delegateCache: WebAuthorizationDelegateCache ) { - private lateinit var customTabLauncher: ActivityResultLauncher + private lateinit var customTabLauncher: ActivityResultLauncher private lateinit var webViewFallbackLauncher: WebViewAuthorizationActivityLauncher companion object { @@ -48,7 +48,7 @@ class POAlternativePaymentMethodCustomTabLauncher private constructor( ).apply { val activityResultHandler = ActivityResultHandler(alternativePaymentMethods, callback) customTabLauncher = from.registerForActivityResult( - CustomTabAuthorizationActivityContract(from.requireActivity()), + POCustomTabAuthorizationActivityContract(from.requireActivity()), activityResultHandler ) webViewFallbackLauncher = WebViewAuthorizationActivityLauncher.create( @@ -70,7 +70,7 @@ class POAlternativePaymentMethodCustomTabLauncher private constructor( ).apply { val activityResultHandler = ActivityResultHandler(alternativePaymentMethods, callback) customTabLauncher = from.registerForActivityResult( - CustomTabAuthorizationActivityContract(from), + POCustomTabAuthorizationActivityContract(from), from.activityResultRegistry, activityResultHandler ) @@ -92,7 +92,7 @@ class POAlternativePaymentMethodCustomTabLauncher private constructor( WebAuthorizationActivityResultDispatcher ).apply { customTabLauncher = from.registerForActivityResult( - CustomTabAuthorizationActivityContract(from.requireActivity()), + POCustomTabAuthorizationActivityContract(from.requireActivity()), activityResultCallback ) webViewFallbackLauncher = WebViewAuthorizationActivityLauncher.create( @@ -113,7 +113,7 @@ class POAlternativePaymentMethodCustomTabLauncher private constructor( WebAuthorizationActivityResultDispatcher ).apply { customTabLauncher = from.registerForActivityResult( - CustomTabAuthorizationActivityContract(from), + POCustomTabAuthorizationActivityContract(from), from.activityResultRegistry, activityResultCallback ) @@ -145,7 +145,7 @@ class POAlternativePaymentMethodCustomTabLauncher private constructor( fun launch(uri: Uri, returnUrl: String) { if (ProcessOut.instance.browserCapabilities.isCustomTabsSupported()) { customTabLauncher.launch( - CustomTabConfiguration( + POCustomTabConfiguration( uri = uri, returnUri = Uri.parse(returnUrl), timeoutSeconds = null @@ -154,7 +154,7 @@ class POAlternativePaymentMethodCustomTabLauncher private constructor( } else { POLogger.info("Custom Chrome Tabs is not supported on device. Will use WebView.") webViewFallbackLauncher.launch( - WebViewConfiguration( + POWebViewConfiguration( uri = uri, returnUris = listOf( Uri.parse(ApiConstants.CHECKOUT_RETURN_URL), diff --git a/sdk/src/main/kotlin/com/processout/sdk/ui/apm/POAlternativePaymentMethodWebViewBuilder.kt b/sdk/src/main/kotlin/com/processout/sdk/ui/apm/POAlternativePaymentMethodWebViewBuilder.kt index 8a16c2199..220fec7fd 100644 --- a/sdk/src/main/kotlin/com/processout/sdk/ui/apm/POAlternativePaymentMethodWebViewBuilder.kt +++ b/sdk/src/main/kotlin/com/processout/sdk/ui/apm/POAlternativePaymentMethodWebViewBuilder.kt @@ -10,7 +10,7 @@ import com.processout.sdk.api.network.ApiConstants import com.processout.sdk.core.ProcessOutResult import com.processout.sdk.ui.web.WebAuthorizationDelegate import com.processout.sdk.ui.web.webview.ProcessOutWebView -import com.processout.sdk.ui.web.webview.WebViewConfiguration +import com.processout.sdk.ui.web.webview.POWebViewConfiguration @Deprecated("Use POAlternativePaymentMethodCustomTabLauncher.") class POAlternativePaymentMethodWebViewBuilder( @@ -30,7 +30,7 @@ class POAlternativePaymentMethodWebViewBuilder( fun build(): WebView = ProcessOutWebView( activity, - WebViewConfiguration( + POWebViewConfiguration( uri = delegate?.uri, returnUris = listOf(Uri.parse(ApiConstants.CHECKOUT_RETURN_URL)), sdkVersion = ProcessOut.VERSION, diff --git a/sdk/src/main/kotlin/com/processout/sdk/ui/threeds/PO3DSRedirectCustomTabLauncher.kt b/sdk/src/main/kotlin/com/processout/sdk/ui/threeds/PO3DSRedirectCustomTabLauncher.kt index d208e3ee0..445ac3f6e 100644 --- a/sdk/src/main/kotlin/com/processout/sdk/ui/threeds/PO3DSRedirectCustomTabLauncher.kt +++ b/sdk/src/main/kotlin/com/processout/sdk/ui/threeds/PO3DSRedirectCustomTabLauncher.kt @@ -9,14 +9,14 @@ import com.processout.sdk.api.network.ApiConstants import com.processout.sdk.core.POFailure import com.processout.sdk.core.ProcessOutResult import com.processout.sdk.core.logger.POLogger -import com.processout.sdk.ui.web.ActivityResultApi +import com.processout.sdk.ui.web.POActivityResultApi import com.processout.sdk.ui.web.WebAuthorizationActivityResultDispatcher import com.processout.sdk.ui.web.WebAuthorizationDelegate import com.processout.sdk.ui.web.WebAuthorizationDelegateCache -import com.processout.sdk.ui.web.customtab.CustomTabAuthorizationActivityContract -import com.processout.sdk.ui.web.customtab.CustomTabConfiguration +import com.processout.sdk.ui.web.customtab.POCustomTabAuthorizationActivityContract +import com.processout.sdk.ui.web.customtab.POCustomTabConfiguration import com.processout.sdk.ui.web.webview.WebViewAuthorizationActivityLauncher -import com.processout.sdk.ui.web.webview.WebViewConfiguration +import com.processout.sdk.ui.web.webview.POWebViewConfiguration /** * Launcher that starts [POCustomTabAuthorizationActivity][com.processout.sdk.ui.web.customtab.POCustomTabAuthorizationActivity] @@ -27,7 +27,7 @@ class PO3DSRedirectCustomTabLauncher private constructor( private val delegateCache: WebAuthorizationDelegateCache ) { - private lateinit var contract: CustomTabAuthorizationActivityContract + private lateinit var contract: POCustomTabAuthorizationActivityContract private lateinit var webViewFallbackLauncher: WebViewAuthorizationActivityLauncher companion object { @@ -37,7 +37,7 @@ class PO3DSRedirectCustomTabLauncher private constructor( fun create(from: Fragment) = PO3DSRedirectCustomTabLauncher( WebAuthorizationActivityResultDispatcher ).apply { - contract = CustomTabAuthorizationActivityContract(from.requireActivity()) + contract = POCustomTabAuthorizationActivityContract(from.requireActivity()) webViewFallbackLauncher = WebViewAuthorizationActivityLauncher.create( from, activityResultCallback = null ) @@ -49,7 +49,7 @@ class PO3DSRedirectCustomTabLauncher private constructor( fun create(from: ComponentActivity) = PO3DSRedirectCustomTabLauncher( WebAuthorizationActivityResultDispatcher ).apply { - contract = CustomTabAuthorizationActivityContract(from) + contract = POCustomTabAuthorizationActivityContract(from) webViewFallbackLauncher = WebViewAuthorizationActivityLauncher.create( from, activityResultCallback = null ) @@ -82,17 +82,17 @@ class PO3DSRedirectCustomTabLauncher private constructor( if (ProcessOut.instance.browserCapabilities.isCustomTabsSupported()) { contract.startActivity( - CustomTabConfiguration( + POCustomTabConfiguration( uri = delegate.uri, returnUri = Uri.parse(returnUrl), timeoutSeconds = redirect.timeoutSeconds, - resultApi = ActivityResultApi.Dispatcher + resultApi = POActivityResultApi.Dispatcher ) ) } else { POLogger.info("Custom Chrome Tabs is not supported on device. Will use WebView.") webViewFallbackLauncher.startActivity( - WebViewConfiguration( + POWebViewConfiguration( uri = delegate.uri, returnUris = listOf( Uri.parse(ApiConstants.CHECKOUT_RETURN_URL), @@ -100,7 +100,7 @@ class PO3DSRedirectCustomTabLauncher private constructor( ), sdkVersion = ProcessOut.VERSION, timeoutSeconds = redirect.timeoutSeconds, - resultApi = ActivityResultApi.Dispatcher + resultApi = POActivityResultApi.Dispatcher ) ) } diff --git a/sdk/src/main/kotlin/com/processout/sdk/ui/threeds/PO3DSRedirectWebViewBuilder.kt b/sdk/src/main/kotlin/com/processout/sdk/ui/threeds/PO3DSRedirectWebViewBuilder.kt index 2ccfba4ea..92a9617c7 100644 --- a/sdk/src/main/kotlin/com/processout/sdk/ui/threeds/PO3DSRedirectWebViewBuilder.kt +++ b/sdk/src/main/kotlin/com/processout/sdk/ui/threeds/PO3DSRedirectWebViewBuilder.kt @@ -9,7 +9,7 @@ import com.processout.sdk.api.network.ApiConstants import com.processout.sdk.core.ProcessOutResult import com.processout.sdk.ui.web.WebAuthorizationDelegate import com.processout.sdk.ui.web.webview.ProcessOutWebView -import com.processout.sdk.ui.web.webview.WebViewConfiguration +import com.processout.sdk.ui.web.webview.POWebViewConfiguration @Deprecated("Use PO3DSRedirectCustomTabLauncher.") class PO3DSRedirectWebViewBuilder( @@ -29,7 +29,7 @@ class PO3DSRedirectWebViewBuilder( fun build(): WebView = ProcessOutWebView( activity, - WebViewConfiguration( + POWebViewConfiguration( uri = delegate?.uri, returnUris = listOf(Uri.parse(ApiConstants.CHECKOUT_RETURN_URL)), sdkVersion = ProcessOut.VERSION, diff --git a/sdk/src/main/kotlin/com/processout/sdk/ui/web/ActivityResultApi.kt b/sdk/src/main/kotlin/com/processout/sdk/ui/web/ActivityResultApi.kt deleted file mode 100644 index 764b2cf4c..000000000 --- a/sdk/src/main/kotlin/com/processout/sdk/ui/web/ActivityResultApi.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.processout.sdk.ui.web - -import android.os.Parcelable -import kotlinx.parcelize.Parcelize - -@Parcelize -internal enum class ActivityResultApi : Parcelable { - Android, - Dispatcher -} diff --git a/sdk/src/main/kotlin/com/processout/sdk/ui/web/POActivityResultApi.kt b/sdk/src/main/kotlin/com/processout/sdk/ui/web/POActivityResultApi.kt new file mode 100644 index 000000000..b504e34d6 --- /dev/null +++ b/sdk/src/main/kotlin/com/processout/sdk/ui/web/POActivityResultApi.kt @@ -0,0 +1,13 @@ +package com.processout.sdk.ui.web + +import android.os.Parcelable +import com.processout.sdk.core.annotation.ProcessOutInternalApi +import kotlinx.parcelize.Parcelize + +/** @suppress */ +@ProcessOutInternalApi +@Parcelize +enum class POActivityResultApi : Parcelable { + Android, + Dispatcher +} diff --git a/sdk/src/main/kotlin/com/processout/sdk/ui/web/customtab/CustomTabAuthorizationViewModel.kt b/sdk/src/main/kotlin/com/processout/sdk/ui/web/customtab/CustomTabAuthorizationViewModel.kt index 3706d94cd..5ccbe7381 100644 --- a/sdk/src/main/kotlin/com/processout/sdk/ui/web/customtab/CustomTabAuthorizationViewModel.kt +++ b/sdk/src/main/kotlin/com/processout/sdk/ui/web/customtab/CustomTabAuthorizationViewModel.kt @@ -10,18 +10,18 @@ import androidx.savedstate.SavedStateRegistryOwner import com.processout.sdk.core.POFailure import com.processout.sdk.core.ProcessOutActivityResult import com.processout.sdk.core.logger.POLogger -import com.processout.sdk.ui.web.customtab.CustomTabAuthorizationActivityContract.Companion.EXTRA_TIMEOUT_FINISH +import com.processout.sdk.ui.web.customtab.POCustomTabAuthorizationActivityContract.Companion.EXTRA_TIMEOUT_FINISH import com.processout.sdk.ui.web.customtab.CustomTabAuthorizationUiState.* import java.util.concurrent.TimeUnit internal class CustomTabAuthorizationViewModel private constructor( private val savedState: SavedStateHandle, - private val configuration: CustomTabConfiguration + private val configuration: POCustomTabConfiguration ) : ViewModel() { internal class Factory( owner: SavedStateRegistryOwner, - private val configuration: CustomTabConfiguration + private val configuration: POCustomTabConfiguration ) : AbstractSavedStateViewModelFactory(owner, defaultArgs = null) { @Suppress("UNCHECKED_CAST") override fun create(key: String, modelClass: Class, handle: SavedStateHandle): T = diff --git a/sdk/src/main/kotlin/com/processout/sdk/ui/web/customtab/CustomTabConfiguration.kt b/sdk/src/main/kotlin/com/processout/sdk/ui/web/customtab/CustomTabConfiguration.kt deleted file mode 100644 index e3c1980fa..000000000 --- a/sdk/src/main/kotlin/com/processout/sdk/ui/web/customtab/CustomTabConfiguration.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.processout.sdk.ui.web.customtab - -import android.net.Uri -import android.os.Parcelable -import com.processout.sdk.ui.web.ActivityResultApi -import kotlinx.parcelize.Parcelize - -@Parcelize -internal data class CustomTabConfiguration( - val uri: Uri, - val returnUri: Uri, - val timeoutSeconds: Int?, - val resultApi: ActivityResultApi = ActivityResultApi.Android -) : Parcelable diff --git a/sdk/src/main/kotlin/com/processout/sdk/ui/web/customtab/POCustomTabAuthorizationActivity.kt b/sdk/src/main/kotlin/com/processout/sdk/ui/web/customtab/POCustomTabAuthorizationActivity.kt index d05738ae0..d0c699881 100644 --- a/sdk/src/main/kotlin/com/processout/sdk/ui/web/customtab/POCustomTabAuthorizationActivity.kt +++ b/sdk/src/main/kotlin/com/processout/sdk/ui/web/customtab/POCustomTabAuthorizationActivity.kt @@ -19,13 +19,13 @@ import com.processout.sdk.core.POFailure import com.processout.sdk.core.ProcessOutActivityResult import com.processout.sdk.core.logger.POLogger import com.processout.sdk.core.onFailure -import com.processout.sdk.ui.web.ActivityResultApi.Android -import com.processout.sdk.ui.web.ActivityResultApi.Dispatcher import com.processout.sdk.ui.web.ActivityResultDispatcher +import com.processout.sdk.ui.web.POActivityResultApi.Android +import com.processout.sdk.ui.web.POActivityResultApi.Dispatcher import com.processout.sdk.ui.web.WebAuthorizationActivityResultDispatcher -import com.processout.sdk.ui.web.customtab.CustomTabAuthorizationActivityContract.Companion.EXTRA_CONFIGURATION -import com.processout.sdk.ui.web.customtab.CustomTabAuthorizationActivityContract.Companion.EXTRA_RESULT -import com.processout.sdk.ui.web.customtab.CustomTabAuthorizationActivityContract.Companion.EXTRA_TIMEOUT_FINISH +import com.processout.sdk.ui.web.customtab.POCustomTabAuthorizationActivityContract.Companion.EXTRA_CONFIGURATION +import com.processout.sdk.ui.web.customtab.POCustomTabAuthorizationActivityContract.Companion.EXTRA_RESULT +import com.processout.sdk.ui.web.customtab.POCustomTabAuthorizationActivityContract.Companion.EXTRA_TIMEOUT_FINISH import com.processout.sdk.ui.web.customtab.CustomTabAuthorizationUiState.* import kotlinx.coroutines.launch @@ -37,7 +37,7 @@ import kotlinx.coroutines.launch class POCustomTabAuthorizationActivity : AppCompatActivity() { private val resultDispatcher: ActivityResultDispatcher = WebAuthorizationActivityResultDispatcher - private lateinit var configuration: CustomTabConfiguration + private lateinit var configuration: POCustomTabConfiguration private val viewModel: CustomTabAuthorizationViewModel by viewModels { CustomTabAuthorizationViewModel.Factory(this, configuration) @@ -57,7 +57,7 @@ class POCustomTabAuthorizationActivity : AppCompatActivity() { // Cancelled result will be provided from onResume() when going back from the Custom Tab. } - intent.getParcelableExtra(EXTRA_CONFIGURATION) + intent.getParcelableExtra(EXTRA_CONFIGURATION) ?.let { configuration = it } if (!::configuration.isInitialized) { diff --git a/sdk/src/main/kotlin/com/processout/sdk/ui/web/customtab/CustomTabAuthorizationActivityContract.kt b/sdk/src/main/kotlin/com/processout/sdk/ui/web/customtab/POCustomTabAuthorizationActivityContract.kt similarity index 82% rename from sdk/src/main/kotlin/com/processout/sdk/ui/web/customtab/CustomTabAuthorizationActivityContract.kt rename to sdk/src/main/kotlin/com/processout/sdk/ui/web/customtab/POCustomTabAuthorizationActivityContract.kt index 7e0901b1e..4caa9ad16 100644 --- a/sdk/src/main/kotlin/com/processout/sdk/ui/web/customtab/CustomTabAuthorizationActivityContract.kt +++ b/sdk/src/main/kotlin/com/processout/sdk/ui/web/customtab/POCustomTabAuthorizationActivityContract.kt @@ -8,11 +8,14 @@ import androidx.activity.result.contract.ActivityResultContract import com.processout.sdk.BuildConfig import com.processout.sdk.core.POFailure import com.processout.sdk.core.ProcessOutActivityResult +import com.processout.sdk.core.annotation.ProcessOutInternalApi import com.processout.sdk.core.logger.POLogger -internal class CustomTabAuthorizationActivityContract( +/** @suppress */ +@ProcessOutInternalApi +class POCustomTabAuthorizationActivityContract( private val activity: ComponentActivity -) : ActivityResultContract>() { +) : ActivityResultContract>() { companion object { const val EXTRA_CONFIGURATION = "${BuildConfig.LIBRARY_PACKAGE_NAME}.EXTRA_CONFIGURATION" @@ -21,13 +24,13 @@ internal class CustomTabAuthorizationActivityContract( const val EXTRA_FORCE_FINISH = "${BuildConfig.LIBRARY_PACKAGE_NAME}.EXTRA_FORCE_FINISH" } - fun startActivity(configuration: CustomTabConfiguration) { + fun startActivity(configuration: POCustomTabConfiguration) { activity.startActivity(createIntent(activity, configuration)) } override fun createIntent( context: Context, - input: CustomTabConfiguration + input: POCustomTabConfiguration ) = Intent(context, POCustomTabAuthorizationActivity::class.java) .putExtra(EXTRA_CONFIGURATION, input) diff --git a/sdk/src/main/kotlin/com/processout/sdk/ui/web/customtab/POCustomTabConfiguration.kt b/sdk/src/main/kotlin/com/processout/sdk/ui/web/customtab/POCustomTabConfiguration.kt new file mode 100644 index 000000000..5e190058c --- /dev/null +++ b/sdk/src/main/kotlin/com/processout/sdk/ui/web/customtab/POCustomTabConfiguration.kt @@ -0,0 +1,17 @@ +package com.processout.sdk.ui.web.customtab + +import android.net.Uri +import android.os.Parcelable +import com.processout.sdk.core.annotation.ProcessOutInternalApi +import com.processout.sdk.ui.web.POActivityResultApi +import kotlinx.parcelize.Parcelize + +/** @suppress */ +@ProcessOutInternalApi +@Parcelize +data class POCustomTabConfiguration( + val uri: Uri, + val returnUri: Uri, + val timeoutSeconds: Int?, + val resultApi: POActivityResultApi = POActivityResultApi.Android +) : Parcelable diff --git a/sdk/src/main/kotlin/com/processout/sdk/ui/web/webview/POWebViewAuthorizationActivity.kt b/sdk/src/main/kotlin/com/processout/sdk/ui/web/webview/POWebViewAuthorizationActivity.kt index 93fa2e6f0..ac84a3385 100644 --- a/sdk/src/main/kotlin/com/processout/sdk/ui/web/webview/POWebViewAuthorizationActivity.kt +++ b/sdk/src/main/kotlin/com/processout/sdk/ui/web/webview/POWebViewAuthorizationActivity.kt @@ -13,12 +13,12 @@ import com.processout.sdk.core.POFailure import com.processout.sdk.core.ProcessOutActivityResult import com.processout.sdk.core.logger.POLogger import com.processout.sdk.core.toActivityResult -import com.processout.sdk.ui.web.ActivityResultApi.Android -import com.processout.sdk.ui.web.ActivityResultApi.Dispatcher +import com.processout.sdk.ui.web.POActivityResultApi.Android +import com.processout.sdk.ui.web.POActivityResultApi.Dispatcher import com.processout.sdk.ui.web.ActivityResultDispatcher import com.processout.sdk.ui.web.WebAuthorizationActivityResultDispatcher -import com.processout.sdk.ui.web.webview.WebViewAuthorizationActivityContract.Companion.EXTRA_CONFIGURATION -import com.processout.sdk.ui.web.webview.WebViewAuthorizationActivityContract.Companion.EXTRA_RESULT +import com.processout.sdk.ui.web.webview.POWebViewAuthorizationActivityContract.Companion.EXTRA_CONFIGURATION +import com.processout.sdk.ui.web.webview.POWebViewAuthorizationActivityContract.Companion.EXTRA_RESULT /** * Activity that handles 3DS and APM authorization in the WebView. @@ -27,7 +27,7 @@ import com.processout.sdk.ui.web.webview.WebViewAuthorizationActivityContract.Co class POWebViewAuthorizationActivity : AppCompatActivity() { private val resultDispatcher: ActivityResultDispatcher = WebAuthorizationActivityResultDispatcher - private lateinit var configuration: WebViewConfiguration + private lateinit var configuration: POWebViewConfiguration @Suppress("DEPRECATION") override fun onCreate(savedInstanceState: Bundle?) { @@ -38,7 +38,7 @@ class POWebViewAuthorizationActivity : AppCompatActivity() { requestedOrientation = if (Build.VERSION.SDK_INT == Build.VERSION_CODES.O) ActivityInfo.SCREEN_ORIENTATION_BEHIND else ActivityInfo.SCREEN_ORIENTATION_PORTRAIT - intent.getParcelableExtra(EXTRA_CONFIGURATION) + intent.getParcelableExtra(EXTRA_CONFIGURATION) ?.let { configuration = it } dispatchBackPressed() setContentView(ProcessOutWebView(this, configuration) { diff --git a/sdk/src/main/kotlin/com/processout/sdk/ui/web/webview/WebViewAuthorizationActivityContract.kt b/sdk/src/main/kotlin/com/processout/sdk/ui/web/webview/POWebViewAuthorizationActivityContract.kt similarity index 84% rename from sdk/src/main/kotlin/com/processout/sdk/ui/web/webview/WebViewAuthorizationActivityContract.kt rename to sdk/src/main/kotlin/com/processout/sdk/ui/web/webview/POWebViewAuthorizationActivityContract.kt index 5a9360ba3..42b5b4b5f 100644 --- a/sdk/src/main/kotlin/com/processout/sdk/ui/web/webview/WebViewAuthorizationActivityContract.kt +++ b/sdk/src/main/kotlin/com/processout/sdk/ui/web/webview/POWebViewAuthorizationActivityContract.kt @@ -9,11 +9,14 @@ import androidx.core.app.ActivityOptionsCompat import com.processout.sdk.BuildConfig import com.processout.sdk.core.POFailure import com.processout.sdk.core.ProcessOutActivityResult +import com.processout.sdk.core.annotation.ProcessOutInternalApi import com.processout.sdk.core.logger.POLogger -internal class WebViewAuthorizationActivityContract( +/** @suppress */ +@ProcessOutInternalApi +class POWebViewAuthorizationActivityContract( private val activity: ComponentActivity -) : ActivityResultContract>() { +) : ActivityResultContract>() { companion object { const val EXTRA_CONFIGURATION = "${BuildConfig.LIBRARY_PACKAGE_NAME}.EXTRA_CONFIGURATION" @@ -22,7 +25,7 @@ internal class WebViewAuthorizationActivityContract( } fun startActivity( - configuration: WebViewConfiguration, + configuration: POWebViewConfiguration, activityOptions: ActivityOptionsCompat ) { activity.startActivity( @@ -33,7 +36,7 @@ internal class WebViewAuthorizationActivityContract( override fun createIntent( context: Context, - input: WebViewConfiguration + input: POWebViewConfiguration ) = Intent(context, POWebViewAuthorizationActivity::class.java) .putExtra(EXTRA_CONFIGURATION, input) diff --git a/sdk/src/main/kotlin/com/processout/sdk/ui/web/webview/WebViewConfiguration.kt b/sdk/src/main/kotlin/com/processout/sdk/ui/web/webview/POWebViewConfiguration.kt similarity index 50% rename from sdk/src/main/kotlin/com/processout/sdk/ui/web/webview/WebViewConfiguration.kt rename to sdk/src/main/kotlin/com/processout/sdk/ui/web/webview/POWebViewConfiguration.kt index 7fdb317e7..8c7876bcf 100644 --- a/sdk/src/main/kotlin/com/processout/sdk/ui/web/webview/WebViewConfiguration.kt +++ b/sdk/src/main/kotlin/com/processout/sdk/ui/web/webview/POWebViewConfiguration.kt @@ -2,14 +2,17 @@ package com.processout.sdk.ui.web.webview import android.net.Uri import android.os.Parcelable -import com.processout.sdk.ui.web.ActivityResultApi +import com.processout.sdk.core.annotation.ProcessOutInternalApi +import com.processout.sdk.ui.web.POActivityResultApi import kotlinx.parcelize.Parcelize +/** @suppress */ +@ProcessOutInternalApi @Parcelize -internal data class WebViewConfiguration( +data class POWebViewConfiguration( val uri: Uri?, val returnUris: List, val sdkVersion: String, val timeoutSeconds: Int?, - val resultApi: ActivityResultApi = ActivityResultApi.Android + val resultApi: POActivityResultApi = POActivityResultApi.Android ) : Parcelable diff --git a/sdk/src/main/kotlin/com/processout/sdk/ui/web/webview/ProcessOutWebView.kt b/sdk/src/main/kotlin/com/processout/sdk/ui/web/webview/ProcessOutWebView.kt index 0a173ee06..d38b8be80 100644 --- a/sdk/src/main/kotlin/com/processout/sdk/ui/web/webview/ProcessOutWebView.kt +++ b/sdk/src/main/kotlin/com/processout/sdk/ui/web/webview/ProcessOutWebView.kt @@ -18,7 +18,7 @@ import java.util.concurrent.TimeUnit @SuppressLint("ViewConstructor", "SetJavaScriptEnabled") internal class ProcessOutWebView( context: Context, - private val configuration: WebViewConfiguration, + private val configuration: POWebViewConfiguration, private val callback: (ProcessOutResult) -> Unit ) : WebView(context) { diff --git a/sdk/src/main/kotlin/com/processout/sdk/ui/web/webview/WebViewAuthorizationActivityLauncher.kt b/sdk/src/main/kotlin/com/processout/sdk/ui/web/webview/WebViewAuthorizationActivityLauncher.kt index 3f0733f1c..2bc33fcce 100644 --- a/sdk/src/main/kotlin/com/processout/sdk/ui/web/webview/WebViewAuthorizationActivityLauncher.kt +++ b/sdk/src/main/kotlin/com/processout/sdk/ui/web/webview/WebViewAuthorizationActivityLauncher.kt @@ -11,8 +11,8 @@ import com.processout.sdk.R import com.processout.sdk.core.ProcessOutActivityResult internal class WebViewAuthorizationActivityLauncher private constructor( - private val contract: WebViewAuthorizationActivityContract, - private val launcher: ActivityResultLauncher?, + private val contract: POWebViewAuthorizationActivityContract, + private val launcher: ActivityResultLauncher?, private val activityOptions: ActivityOptionsCompat ) { @@ -21,7 +21,7 @@ internal class WebViewAuthorizationActivityLauncher private constructor( from: Fragment, activityResultCallback: ActivityResultCallback>? ): WebViewAuthorizationActivityLauncher { - val contract = WebViewAuthorizationActivityContract(from.requireActivity()) + val contract = POWebViewAuthorizationActivityContract(from.requireActivity()) return WebViewAuthorizationActivityLauncher( contract = contract, launcher = activityResultCallback?.let { callback -> @@ -35,7 +35,7 @@ internal class WebViewAuthorizationActivityLauncher private constructor( from: ComponentActivity, activityResultCallback: ActivityResultCallback>? ): WebViewAuthorizationActivityLauncher { - val contract = WebViewAuthorizationActivityContract(from) + val contract = POWebViewAuthorizationActivityContract(from) return WebViewAuthorizationActivityLauncher( contract = contract, launcher = activityResultCallback?.let { callback -> @@ -53,11 +53,11 @@ internal class WebViewAuthorizationActivityLauncher private constructor( ) } - fun startActivity(configuration: WebViewConfiguration) { + fun startActivity(configuration: POWebViewConfiguration) { contract.startActivity(configuration, activityOptions) } - fun launch(configuration: WebViewConfiguration) { + fun launch(configuration: POWebViewConfiguration) { launcher?.launch(configuration, activityOptions) } } From cc7a8f32c277395880a03a7fbe9a19f7fc510014 Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Wed, 4 Dec 2024 19:54:17 +0200 Subject: [PATCH 13/16] Handle EXTRA_FORCE_FINISH in POCustomTabAuthorizationActivity --- .../CustomTabAuthorizationViewModel.kt | 7 ++++++- .../POCustomTabAuthorizationActivity.kt | 17 ++++++++++++----- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/sdk/src/main/kotlin/com/processout/sdk/ui/web/customtab/CustomTabAuthorizationViewModel.kt b/sdk/src/main/kotlin/com/processout/sdk/ui/web/customtab/CustomTabAuthorizationViewModel.kt index 5ccbe7381..a2814df51 100644 --- a/sdk/src/main/kotlin/com/processout/sdk/ui/web/customtab/CustomTabAuthorizationViewModel.kt +++ b/sdk/src/main/kotlin/com/processout/sdk/ui/web/customtab/CustomTabAuthorizationViewModel.kt @@ -10,8 +10,9 @@ import androidx.savedstate.SavedStateRegistryOwner import com.processout.sdk.core.POFailure import com.processout.sdk.core.ProcessOutActivityResult import com.processout.sdk.core.logger.POLogger -import com.processout.sdk.ui.web.customtab.POCustomTabAuthorizationActivityContract.Companion.EXTRA_TIMEOUT_FINISH import com.processout.sdk.ui.web.customtab.CustomTabAuthorizationUiState.* +import com.processout.sdk.ui.web.customtab.POCustomTabAuthorizationActivityContract.Companion.EXTRA_FORCE_FINISH +import com.processout.sdk.ui.web.customtab.POCustomTabAuthorizationActivityContract.Companion.EXTRA_TIMEOUT_FINISH import java.util.concurrent.TimeUnit internal class CustomTabAuthorizationViewModel private constructor( @@ -49,6 +50,10 @@ internal class CustomTabAuthorizationViewModel private constructor( } fun onResume(intent: Intent) { + if (intent.getBooleanExtra(EXTRA_FORCE_FINISH, false)) { + savedState[KEY_SAVED_STATE] = Cancelled + return + } if (intent.getBooleanExtra(EXTRA_TIMEOUT_FINISH, false)) { savedState[KEY_SAVED_STATE] = Timeout(clearBackStack = false) return diff --git a/sdk/src/main/kotlin/com/processout/sdk/ui/web/customtab/POCustomTabAuthorizationActivity.kt b/sdk/src/main/kotlin/com/processout/sdk/ui/web/customtab/POCustomTabAuthorizationActivity.kt index d0c699881..6cb5909f8 100644 --- a/sdk/src/main/kotlin/com/processout/sdk/ui/web/customtab/POCustomTabAuthorizationActivity.kt +++ b/sdk/src/main/kotlin/com/processout/sdk/ui/web/customtab/POCustomTabAuthorizationActivity.kt @@ -23,10 +23,11 @@ import com.processout.sdk.ui.web.ActivityResultDispatcher import com.processout.sdk.ui.web.POActivityResultApi.Android import com.processout.sdk.ui.web.POActivityResultApi.Dispatcher import com.processout.sdk.ui.web.WebAuthorizationActivityResultDispatcher +import com.processout.sdk.ui.web.customtab.CustomTabAuthorizationUiState.* import com.processout.sdk.ui.web.customtab.POCustomTabAuthorizationActivityContract.Companion.EXTRA_CONFIGURATION +import com.processout.sdk.ui.web.customtab.POCustomTabAuthorizationActivityContract.Companion.EXTRA_FORCE_FINISH import com.processout.sdk.ui.web.customtab.POCustomTabAuthorizationActivityContract.Companion.EXTRA_RESULT import com.processout.sdk.ui.web.customtab.POCustomTabAuthorizationActivityContract.Companion.EXTRA_TIMEOUT_FINISH -import com.processout.sdk.ui.web.customtab.CustomTabAuthorizationUiState.* import kotlinx.coroutines.launch /** @@ -57,13 +58,19 @@ class POCustomTabAuthorizationActivity : AppCompatActivity() { // Cancelled result will be provided from onResume() when going back from the Custom Tab. } + if (intent.getBooleanExtra(EXTRA_FORCE_FINISH, false)) { + POLogger.info("Activity is started to clear the back stack and finished immediately before it's created.") + finish() + return + } + intent.getParcelableExtra(EXTRA_CONFIGURATION) ?.let { configuration = it } if (!::configuration.isInitialized) { POLogger.info( - "Configuration is not provided. Possibly started to clear the back stack and force finish. " + - "Also possibly started from the redirect activity by a deep link when flow is already finished." + "Configuration is not provided. Activity is finished immediately before it's created. " + + "Possibly started from the redirect activity by a deep link when the flow is already finished." ) finish() return @@ -95,8 +102,8 @@ class POCustomTabAuthorizationActivity : AppCompatActivity() { is Failure -> finishWithActivityResult(uiState.failure) Cancelled -> finishWithActivityResult( ProcessOutActivityResult.Failure( - POFailure.Code.Cancelled, - "Cancelled by the user with back press, gesture or cancel action." + code = POFailure.Code.Cancelled, + message = "Cancelled by the user with back press, gesture or cancel action." ) ) is Timeout -> handleTimeout(uiState.clearBackStack) From 60e229b1d4d49067da52a4c5e0bc2d28d4a60cf1 Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Wed, 4 Dec 2024 20:17:19 +0200 Subject: [PATCH 14/16] Handle EXTRA_FORCE_FINISH in POWebViewAuthorizationActivity --- .../webview/POWebViewAuthorizationActivity.kt | 32 +++++++++++++++++-- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/sdk/src/main/kotlin/com/processout/sdk/ui/web/webview/POWebViewAuthorizationActivity.kt b/sdk/src/main/kotlin/com/processout/sdk/ui/web/webview/POWebViewAuthorizationActivity.kt index ac84a3385..93a4ed1e1 100644 --- a/sdk/src/main/kotlin/com/processout/sdk/ui/web/webview/POWebViewAuthorizationActivity.kt +++ b/sdk/src/main/kotlin/com/processout/sdk/ui/web/webview/POWebViewAuthorizationActivity.kt @@ -13,11 +13,12 @@ import com.processout.sdk.core.POFailure import com.processout.sdk.core.ProcessOutActivityResult import com.processout.sdk.core.logger.POLogger import com.processout.sdk.core.toActivityResult +import com.processout.sdk.ui.web.ActivityResultDispatcher import com.processout.sdk.ui.web.POActivityResultApi.Android import com.processout.sdk.ui.web.POActivityResultApi.Dispatcher -import com.processout.sdk.ui.web.ActivityResultDispatcher import com.processout.sdk.ui.web.WebAuthorizationActivityResultDispatcher import com.processout.sdk.ui.web.webview.POWebViewAuthorizationActivityContract.Companion.EXTRA_CONFIGURATION +import com.processout.sdk.ui.web.webview.POWebViewAuthorizationActivityContract.Companion.EXTRA_FORCE_FINISH import com.processout.sdk.ui.web.webview.POWebViewAuthorizationActivityContract.Companion.EXTRA_RESULT /** @@ -38,20 +39,45 @@ class POWebViewAuthorizationActivity : AppCompatActivity() { requestedOrientation = if (Build.VERSION.SDK_INT == Build.VERSION_CODES.O) ActivityInfo.SCREEN_ORIENTATION_BEHIND else ActivityInfo.SCREEN_ORIENTATION_PORTRAIT + if (intent.getBooleanExtra(EXTRA_FORCE_FINISH, false)) { + POLogger.info("Activity is started to clear the back stack and finished immediately before it's created.") + finish() + return + } + intent.getParcelableExtra(EXTRA_CONFIGURATION) ?.let { configuration = it } + + if (!::configuration.isInitialized) { + POLogger.info("Configuration is not provided. Activity is finished immediately before it's created.") + finish() + return + } + dispatchBackPressed() setContentView(ProcessOutWebView(this, configuration) { finishWithActivityResult(it.toActivityResult()) }) } + override fun onNewIntent(intent: Intent) { + super.onNewIntent(intent) + if (intent.getBooleanExtra(EXTRA_FORCE_FINISH, false)) { + finishWithActivityResult( + ProcessOutActivityResult.Failure( + code = POFailure.Code.Cancelled, + message = "Cancelled by the user with cancel action." + ).also { POLogger.info("%s", it) } + ) + } + } + private fun dispatchBackPressed() { onBackPressedDispatcher.addCallback(this) { finishWithActivityResult( ProcessOutActivityResult.Failure( - POFailure.Code.Cancelled, - "Cancelled by user with back press or gesture." + code = POFailure.Code.Cancelled, + message = "Cancelled by the user with back press or gesture." ).also { POLogger.info("%s", it) } ) } From 1296e92254b6f1c0948df9370ddd078532254e3f Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Thu, 5 Dec 2024 15:22:44 +0200 Subject: [PATCH 15/16] Cancel web authorization --- .../ui/checkout/DynamicCheckoutActivity.kt | 27 ++++++++++++++++++- .../sdk/ui/checkout/DynamicCheckoutEvent.kt | 2 ++ 2 files changed, 28 insertions(+), 1 deletion(-) 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 dfa715e08..aa6287855 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 @@ -18,9 +18,11 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat +import androidx.lifecycle.Lifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.google.android.gms.wallet.Wallet.WalletOptions import com.google.android.gms.wallet.WalletConstants +import com.processout.sdk.api.ProcessOut import com.processout.sdk.api.dispatcher.card.tokenization.PODefaultCardTokenizationEventDispatcher import com.processout.sdk.api.dispatcher.napm.PODefaultNativeAlternativePaymentMethodEventDispatcher import com.processout.sdk.api.model.request.POInvoiceRequest @@ -54,6 +56,10 @@ import com.processout.sdk.ui.napm.PONativeAlternativePaymentConfiguration.Paymen import com.processout.sdk.ui.shared.configuration.POBarcodeConfiguration import com.processout.sdk.ui.shared.configuration.POCancellationConfiguration import com.processout.sdk.ui.shared.extension.collectImmediately +import com.processout.sdk.ui.web.customtab.POCustomTabAuthorizationActivity +import com.processout.sdk.ui.web.customtab.POCustomTabAuthorizationActivityContract +import com.processout.sdk.ui.web.webview.POWebViewAuthorizationActivity +import com.processout.sdk.ui.web.webview.POWebViewAuthorizationActivityContract internal class DynamicCheckoutActivity : BaseTransparentPortraitActivity() { @@ -167,7 +173,9 @@ internal class DynamicCheckoutActivity : BaseTransparentPortraitActivity() { with(viewModel.completion.collectAsStateWithLifecycle()) { LaunchedEffect(value) { handle(value) } } - viewModel.sideEffects.collectImmediately { handle(it) } + viewModel.sideEffects.collectImmediately( + minActiveState = Lifecycle.State.CREATED + ) { handle(it) } DynamicCheckoutScreen( state = viewModel.state.collectAsStateWithLifecycle().value, onEvent = remember { viewModel::onEvent }, @@ -225,6 +233,7 @@ internal class DynamicCheckoutActivity : BaseTransparentPortraitActivity() { ) } is PermissionRequest -> requestPermission(sideEffect) + is CancelWebAuthorization -> cancelWebAuthorization() } } @@ -295,6 +304,22 @@ internal class DynamicCheckoutActivity : BaseTransparentPortraitActivity() { } } + private fun cancelWebAuthorization() { + if (ProcessOut.instance.browserCapabilities.isCustomTabsSupported()) { + Intent(this, POCustomTabAuthorizationActivity::class.java).let { + it.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP + it.putExtra(POCustomTabAuthorizationActivityContract.EXTRA_FORCE_FINISH, true) + startActivity(it) + } + } else { + Intent(this, POWebViewAuthorizationActivity::class.java).let { + it.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP + it.putExtra(POWebViewAuthorizationActivityContract.EXTRA_FORCE_FINISH, true) + startActivity(it) + } + } + } + private fun handle(completion: DynamicCheckoutCompletion) = when (completion) { Success -> finishWithActivityResult( diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/checkout/DynamicCheckoutEvent.kt b/ui/src/main/kotlin/com/processout/sdk/ui/checkout/DynamicCheckoutEvent.kt index 1fe18d8e6..7ee070922 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/checkout/DynamicCheckoutEvent.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/checkout/DynamicCheckoutEvent.kt @@ -75,6 +75,8 @@ internal sealed interface DynamicCheckoutSideEffect { val paymentMethodId: String, val permission: String ) : DynamicCheckoutSideEffect + + data object CancelWebAuthorization : DynamicCheckoutSideEffect } internal sealed interface DynamicCheckoutCompletion { From 52d0931393df4f5f0379841a4a64f5286cdabd0d Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Thu, 5 Dec 2024 19:32:42 +0200 Subject: [PATCH 16/16] DynamicCheckoutInteractor - AuthorizeInvoiceJob, restart/cancellation logic, correct state --- .../ui/checkout/DynamicCheckoutInteractor.kt | 59 ++++++++++++++----- 1 file changed, 44 insertions(+), 15 deletions(-) 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 c7099d053..34131d595 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 @@ -22,6 +22,7 @@ import com.processout.sdk.api.model.response.POBillingAddressCollectionMode.* import com.processout.sdk.api.model.response.PODynamicCheckoutPaymentMethod.* import com.processout.sdk.api.model.response.PODynamicCheckoutPaymentMethod.Flow.express import com.processout.sdk.api.model.response.POTransaction.Status.* +import com.processout.sdk.api.service.PO3DSService import com.processout.sdk.api.service.POInvoicesService import com.processout.sdk.api.service.googlepay.POGooglePayConfiguration import com.processout.sdk.api.service.googlepay.POGooglePayConfiguration.CardConfiguration.BillingAddressParameters @@ -104,6 +105,7 @@ internal class DynamicCheckoutInteractor( private val handler = Handler(Looper.getMainLooper()) + private var authorizeInvoiceJob: AuthorizeInvoiceJob? = null private var latestInvoiceRequest: PODynamicCheckoutInvoiceRequest? = null init { @@ -136,23 +138,22 @@ internal class DynamicCheckoutInteractor( } private fun restart(invoiceRequest: POInvoiceRequest) { + interactorScope.coroutineContext.cancelChildren() + handler.removeCallbacksAndMessages(null) + cancelWebAuthorization() configuration = configuration.copy(invoiceRequest = invoiceRequest) logAttributes = logAttributes(invoiceId = invoiceRequest.invoiceId) - reset() interactorScope.launch { start() } } - private fun reset() { - interactorScope.coroutineContext.cancelChildren() - handler.removeCallbacksAndMessages(null) - latestInvoiceRequest = null - resetPaymentMethods() - _completion.update { Awaiting } - } - - private fun resetPaymentMethods() { - cardTokenization.reset() - nativeAlternativePayment.reset() + private fun cancelWebAuthorization() { + with(_state.value) { + if (selectedPaymentMethod != null || pendingSubmitPaymentMethod != null) { + interactorScope.launch { + _sideEffects.send(DynamicCheckoutSideEffect.CancelWebAuthorization) + } + } + } } private suspend fun fetchConfiguration() { @@ -437,12 +438,20 @@ internal class DynamicCheckoutInteractor( _state.update { it.copy( selectedPaymentMethod = paymentMethod, + pendingSubmitPaymentMethod = null, errorMessage = null ) } } } + private fun resetPaymentMethods() { + cardTokenization.reset() + nativeAlternativePayment.reset() + authorizeInvoiceJob?.cancel() + authorizeInvoiceJob = null + } + private fun start(paymentMethod: PaymentMethod) { when (paymentMethod) { is Card -> cardTokenization.start( @@ -591,6 +600,10 @@ internal class DynamicCheckoutInteractor( ) } } + if (_state.value.invoice == null) { + _state.update { it.copy(pendingSubmitPaymentMethod = paymentMethod) } + return + } if (_state.value.processingPaymentMethod != null) { _state.update { it.copy(pendingSubmitPaymentMethod = paymentMethod) } invalidateInvoice( @@ -741,6 +754,7 @@ internal class DynamicCheckoutInteractor( } } val currentInvoice = _state.value.invoice ?: POInvoice(id = configuration.invoiceRequest.invoiceId) + resetPaymentMethods() _state.update { it.copy( invoice = null, @@ -823,14 +837,15 @@ internal class DynamicCheckoutInteractor( } } + @Suppress("DEPRECATION") private fun collectInvoiceAuthorizationRequest() { eventDispatcher.subscribeForResponse( coroutineScope = interactorScope ) { response -> - @Suppress("DEPRECATION") - invoicesService.authorizeInvoice( + val threeDSService = PODefaultProxy3DSService() + val job = invoicesService.authorizeInvoice( request = response.request, - threeDSService = PODefaultProxy3DSService() + threeDSService = threeDSService ) { result -> handleInvoiceAuthorization( state = _state.value, @@ -838,6 +853,10 @@ internal class DynamicCheckoutInteractor( result = result ) } + authorizeInvoiceJob = AuthorizeInvoiceJob( + job = job, + threeDSService = threeDSService + ) } } @@ -1010,4 +1029,14 @@ internal class DynamicCheckoutInteractor( fun onCleared() { handler.removeCallbacksAndMessages(null) } + + private class AuthorizeInvoiceJob( + val job: Job, + val threeDSService: PO3DSService + ) { + fun cancel() { + job.cancel() + threeDSService.cleanup() + } + } }