From 781084ad767e97d9da6a8c155ac6388ae30361a4 Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Mon, 21 Oct 2024 14:11:38 +0300 Subject: [PATCH 01/37] PODynamicCheckoutEvent --- .../api/model/event/PODynamicCheckoutEvent.kt | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 sdk/src/main/kotlin/com/processout/sdk/api/model/event/PODynamicCheckoutEvent.kt diff --git a/sdk/src/main/kotlin/com/processout/sdk/api/model/event/PODynamicCheckoutEvent.kt b/sdk/src/main/kotlin/com/processout/sdk/api/model/event/PODynamicCheckoutEvent.kt new file mode 100644 index 00000000..e2106517 --- /dev/null +++ b/sdk/src/main/kotlin/com/processout/sdk/api/model/event/PODynamicCheckoutEvent.kt @@ -0,0 +1,57 @@ +package com.processout.sdk.api.model.event + +import com.processout.sdk.api.model.response.PODynamicCheckoutPaymentMethod +import com.processout.sdk.core.ProcessOutResult + +/** + * Defines dynamic checkout lifecycle events. + */ +sealed class PODynamicCheckoutEvent { + + /** + * Initial event that is sent prior any other event. + */ + data object WillStart : PODynamicCheckoutEvent() + + /** + * Event indicates that initialization is complete. + * Currently waiting for user input. + */ + data object DidStart : PODynamicCheckoutEvent() + + /** + * Event is sent when user attempts to select payment method. + */ + data class WillSelectPaymentMethod( + val paymentMethod: PODynamicCheckoutPaymentMethod + ) : PODynamicCheckoutEvent() + + /** + * Event is sent when payment method is selected by the user. + */ + data class DidSelectPaymentMethod( + val paymentMethod: PODynamicCheckoutPaymentMethod + ) : PODynamicCheckoutEvent() + + /** + * Event is sent when payment method selection has failed. + */ + data class DidFailToSelectPaymentMethod( + val failure: ProcessOutResult.Failure, + val paymentMethod: PODynamicCheckoutPaymentMethod + ) : PODynamicCheckoutEvent() + + /** + * Event is sent after payment was confirmed to be captured. This is a final event. + */ + data class DidCompletePayment( + val paymentMethod: PODynamicCheckoutPaymentMethod + ) : PODynamicCheckoutEvent() + + /** + * Event is sent when unretryable error occurs. This is a final event. + */ + data class DidFail( + val failure: ProcessOutResult.Failure + ) : PODynamicCheckoutEvent() +} From 4e0696d001639c7f53b70f131c63b4225a433c43 Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Mon, 21 Oct 2024 15:21:56 +0300 Subject: [PATCH 02/37] Dispatch events to delegate --- .../processout/sdk/ui/checkout/PODynamicCheckoutDelegate.kt | 3 +++ .../processout/sdk/ui/checkout/PODynamicCheckoutLauncher.kt | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/checkout/PODynamicCheckoutDelegate.kt b/ui/src/main/kotlin/com/processout/sdk/ui/checkout/PODynamicCheckoutDelegate.kt index 513d683e..7a8f3cb9 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/checkout/PODynamicCheckoutDelegate.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/checkout/PODynamicCheckoutDelegate.kt @@ -1,6 +1,7 @@ package com.processout.sdk.ui.checkout import com.processout.sdk.api.model.event.POCardTokenizationEvent +import com.processout.sdk.api.model.event.PODynamicCheckoutEvent import com.processout.sdk.api.model.event.PONativeAlternativePaymentMethodEvent import com.processout.sdk.api.model.request.PODynamicCheckoutInvoiceInvalidationReason import com.processout.sdk.api.model.request.POInvoiceAuthorizationRequest @@ -13,6 +14,8 @@ import com.processout.sdk.ui.core.annotation.ProcessOutInternalApi @ProcessOutInternalApi interface PODynamicCheckoutDelegate { + fun onEvent(event: PODynamicCheckoutEvent) {} + fun onEvent(event: POCardTokenizationEvent) {} fun onEvent(event: PONativeAlternativePaymentMethodEvent) {} 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 32df29a1..9df040c7 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 @@ -8,6 +8,7 @@ import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope import com.processout.sdk.api.dispatcher.POEventDispatcher import com.processout.sdk.api.model.event.POCardTokenizationEvent +import com.processout.sdk.api.model.event.PODynamicCheckoutEvent import com.processout.sdk.api.model.event.PONativeAlternativePaymentMethodEvent import com.processout.sdk.api.model.request.PODynamicCheckoutInvoiceAuthorizationRequest import com.processout.sdk.api.model.request.PODynamicCheckoutInvoiceRequest @@ -92,6 +93,9 @@ class PODynamicCheckoutLauncher private constructor( } private fun dispatchEvents() { + eventDispatcher.subscribe( + coroutineScope = scope + ) { delegate.onEvent(it) } eventDispatcher.subscribe( coroutineScope = scope ) { delegate.onEvent(it) } From de604fa67978681809f87bd19d79fcf21b237dbc Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Mon, 21 Oct 2024 15:45:41 +0300 Subject: [PATCH 03/37] dispatchFailure() --- .../ui/checkout/DynamicCheckoutInteractor.kt | 69 ++++++++++++------- 1 file changed, 44 insertions(+), 25 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 e8e943ae..280ce94e 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 @@ -12,6 +12,8 @@ import com.processout.sdk.R import com.processout.sdk.api.dispatcher.POEventDispatcher import com.processout.sdk.api.dispatcher.card.tokenization.PODefaultCardTokenizationEventDispatcher import com.processout.sdk.api.dispatcher.napm.PODefaultNativeAlternativePaymentMethodEventDispatcher +import com.processout.sdk.api.model.event.PODynamicCheckoutEvent +import com.processout.sdk.api.model.event.PODynamicCheckoutEvent.DidFail import com.processout.sdk.api.model.event.PONativeAlternativePaymentMethodEvent.WillSubmitParameters import com.processout.sdk.api.model.request.* import com.processout.sdk.api.model.response.* @@ -101,6 +103,7 @@ internal class DynamicCheckoutInteractor( private fun start() { handleCompletions() dispatchEvents() + dispatchFailure() collectInvoice() collectInvoiceAuthorizationRequest() collectAuthorizeInvoiceResult() @@ -626,17 +629,6 @@ internal class DynamicCheckoutInteractor( } } - private fun cancel() { - _completion.update { - Failure( - ProcessOutResult.Failure( - code = Cancelled, - message = "Cancelled by the user with cancel action." - ).also { POLogger.info("Cancelled: %s", it) } - ) - } - } - private fun invalidateInvoice(reason: PODynamicCheckoutInvoiceInvalidationReason) { interactorScope.launch { _state.update { it.copy(isInvoiceValid = false) } @@ -677,20 +669,6 @@ internal class DynamicCheckoutInteractor( } } - private fun dispatchEvents() { - interactorScope.launch { - cardTokenizationEventDispatcher.events.collect { eventDispatcher.send(it) } - } - interactorScope.launch { - nativeAlternativePaymentEventDispatcher.events.collect { event -> - if (event is WillSubmitParameters) { - _state.update { it.copy(processingPaymentMethodId = selectedPaymentMethod()?.id) } - } - eventDispatcher.send(event) - } - } - } - private fun collectTokenizedCard() { interactorScope.launch { cardTokenizationEventDispatcher.processTokenizedCardRequest.collect { request -> @@ -812,6 +790,47 @@ internal class DynamicCheckoutInteractor( } ?: _completion.update { Success } } + private fun dispatch(event: PODynamicCheckoutEvent) { + interactorScope.launch { + eventDispatcher.send(event) + } + } + + private fun dispatchEvents() { + interactorScope.launch { + cardTokenizationEventDispatcher.events.collect { eventDispatcher.send(it) } + } + interactorScope.launch { + nativeAlternativePaymentEventDispatcher.events.collect { event -> + if (event is WillSubmitParameters) { + _state.update { it.copy(processingPaymentMethodId = selectedPaymentMethod()?.id) } + } + eventDispatcher.send(event) + } + } + } + + private fun dispatchFailure() { + interactorScope.launch { + _completion.collect { + if (it is Failure) { + dispatch(DidFail(it.failure)) + } + } + } + } + + private fun cancel() { + _completion.update { + Failure( + ProcessOutResult.Failure( + code = Cancelled, + message = "Cancelled by the user with cancel action." + ).also { POLogger.info("Cancelled: %s", it) } + ) + } + } + fun onCleared() { threeDSService.close() handler.removeCallbacksAndMessages(null) From 266fd2f18c7db25ae53146061f90376df0e9a529 Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Mon, 21 Oct 2024 17:10:44 +0300 Subject: [PATCH 04/37] logAttributes --- .../sdk/ui/checkout/DynamicCheckoutInteractor.kt | 12 +++++++++++- 1 file changed, 11 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 280ce94e..c0480542 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 @@ -32,6 +32,7 @@ import com.processout.sdk.api.service.googlepay.POGooglePayService import com.processout.sdk.api.service.proxy3ds.POProxy3DSService import com.processout.sdk.core.POFailure.Code.* import com.processout.sdk.core.ProcessOutResult +import com.processout.sdk.core.logger.POLogAttribute import com.processout.sdk.core.logger.POLogger import com.processout.sdk.core.onFailure import com.processout.sdk.core.onSuccess @@ -77,9 +78,17 @@ internal class DynamicCheckoutInteractor( private val cardTokenizationEventDispatcher: PODefaultCardTokenizationEventDispatcher, private val nativeAlternativePayment: NativeAlternativePaymentViewModel, private val nativeAlternativePaymentEventDispatcher: PODefaultNativeAlternativePaymentMethodEventDispatcher, - private val eventDispatcher: POEventDispatcher = POEventDispatcher + private val eventDispatcher: POEventDispatcher = POEventDispatcher, + private var logAttributes: Map = logAttributes( + invoiceId = configuration.invoiceRequest.invoiceId + ) ) : BaseInteractor() { + private companion object { + fun logAttributes(invoiceId: String): Map = + mapOf(POLogAttribute.INVOICE_ID to invoiceId) + } + private val _completion = MutableStateFlow(Awaiting) val completion = _completion.asStateFlow() @@ -116,6 +125,7 @@ internal class DynamicCheckoutInteractor( reason: PODynamicCheckoutInvoiceInvalidationReason ) { configuration = configuration.copy(invoiceRequest = invoiceRequest) + logAttributes = logAttributes(invoiceId = invoiceRequest.invoiceId) val selectedPaymentMethodId = when (reason) { is PODynamicCheckoutInvoiceInvalidationReason.Failure -> if (reason.failure.code == Cancelled) From fa4504c14527cece875a3d17823e7abf0894f0db Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Mon, 21 Oct 2024 20:19:51 +0300 Subject: [PATCH 05/37] preloadAllImages() in separate coroutineScope (still suspending) --- .../ui/checkout/DynamicCheckoutInteractor.kt | 36 +++++++++---------- 1 file changed, 16 insertions(+), 20 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 c0480542..ce7b159d 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 @@ -207,10 +207,7 @@ internal class DynamicCheckoutInteractor( amount = invoice.amount, currency = invoice.currency ) - preloadAllImages( - paymentMethods = mappedPaymentMethods, - coroutineScope = this@launch - ) + preloadAllImages(paymentMethods = mappedPaymentMethods) _state.update { it.copy( loading = false, @@ -355,24 +352,23 @@ internal class DynamicCheckoutInteractor( //region Images - private suspend fun preloadAllImages( - paymentMethods: List, - coroutineScope: CoroutineScope - ) { - val logoUrls = mutableListOf() - paymentMethods.forEach { - when (it) { - is Card -> logoUrls.addAll(it.display.logoUrls()) - is AlternativePayment -> logoUrls.addAll(it.display.logoUrls()) - is NativeAlternativePayment -> logoUrls.addAll(it.display.logoUrls()) - is CustomerToken -> logoUrls.addAll(it.display.logoUrls()) - else -> {} + private suspend fun preloadAllImages(paymentMethods: List) { + coroutineScope { + val logoUrls = mutableListOf() + paymentMethods.forEach { + when (it) { + is Card -> logoUrls.addAll(it.display.logoUrls()) + is AlternativePayment -> logoUrls.addAll(it.display.logoUrls()) + is NativeAlternativePayment -> logoUrls.addAll(it.display.logoUrls()) + is CustomerToken -> logoUrls.addAll(it.display.logoUrls()) + else -> {} + } } + val deferredResults = logoUrls.map { url -> + async { preloadImage(url) } + } + deferredResults.awaitAll() } - val deferredResults = logoUrls.map { url -> - coroutineScope.async { preloadImage(url) } - } - deferredResults.awaitAll() } private fun Display.logoUrls(): List { From 4390cb55c99e0cac177d72dada0e904bff979928 Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Mon, 21 Oct 2024 20:39:33 +0300 Subject: [PATCH 06/37] Start DC in single coroutine with suspend functions instead of launching separate coroutines --- .../ui/checkout/DynamicCheckoutInteractor.kt | 122 +++++++++--------- 1 file changed, 60 insertions(+), 62 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 ce7b159d..b498a2e6 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 @@ -106,10 +106,12 @@ internal class DynamicCheckoutInteractor( private var latestInvoiceRequest: PODynamicCheckoutInvoiceRequest? = null init { - start() + interactorScope.launch { + start() + } } - private fun start() { + private suspend fun start() { handleCompletions() dispatchEvents() dispatchFailure() @@ -146,7 +148,7 @@ internal class DynamicCheckoutInteractor( errorMessage = errorMessage ) ) - start() + interactorScope.launch { start() } } private fun reset(state: DynamicCheckoutInteractorState) { @@ -167,75 +169,71 @@ internal class DynamicCheckoutInteractor( cancelActionId = ActionId.CANCEL ) - private fun fetchConfiguration() { - interactorScope.launch { - invoicesService.invoice(configuration.invoiceRequest) - .onSuccess { invoice -> - when (invoice.transaction?.status()) { - WAITING -> setStartedState(invoice) - AUTHORIZED, COMPLETED -> handleSuccess() - else -> _completion.update { - Failure( - ProcessOutResult.Failure( - code = Generic(), - message = "Unsupported invoice state. Please create new invoice and restart dynamic checkout." - ) + private suspend fun fetchConfiguration() { + invoicesService.invoice(configuration.invoiceRequest) + .onSuccess { invoice -> + when (invoice.transaction?.status()) { + WAITING -> setStartedState(invoice) + AUTHORIZED, COMPLETED -> handleSuccess() + else -> _completion.update { + Failure( + ProcessOutResult.Failure( + code = Generic(), + message = "Unsupported invoice state. Please create new invoice and restart dynamic checkout." ) - } + ) } - }.onFailure { failure -> - _completion.update { Failure(failure) } } - } + }.onFailure { failure -> + _completion.update { Failure(failure) } + } } - private fun setStartedState(invoice: POInvoice) { - interactorScope.launch { - val paymentMethods = invoice.paymentMethods - if (paymentMethods.isNullOrEmpty()) { - _completion.update { - Failure( - ProcessOutResult.Failure( - code = Generic(), - message = "Missing payment methods configuration." - ) + private suspend fun setStartedState(invoice: POInvoice) { + val paymentMethods = invoice.paymentMethods + if (paymentMethods.isNullOrEmpty()) { + _completion.update { + Failure( + ProcessOutResult.Failure( + code = Generic(), + message = "Missing payment methods configuration." ) - } - return@launch - } - val mappedPaymentMethods = paymentMethods.map( - amount = invoice.amount, - currency = invoice.currency - ) - preloadAllImages(paymentMethods = mappedPaymentMethods) - _state.update { - it.copy( - loading = false, - invoice = invoice, - isInvoiceValid = true, - paymentMethods = mappedPaymentMethods ) } - _state.value.selectedPaymentMethodId?.let { id -> - paymentMethod(id)?.let { start(it) } - .orElse { - _state.update { - it.copy( - selectedPaymentMethodId = null, - errorMessage = app.getString(R.string.po_dynamic_checkout_error_method_unavailable) - ) - } + return + } + val mappedPaymentMethods = paymentMethods.map( + amount = invoice.amount, + currency = invoice.currency + ) + preloadAllImages(paymentMethods = mappedPaymentMethods) + _state.update { + it.copy( + loading = false, + invoice = invoice, + isInvoiceValid = true, + paymentMethods = mappedPaymentMethods + ) + } + _state.value.selectedPaymentMethodId?.let { id -> + paymentMethod(id)?.let { start(it) } + .orElse { + _state.update { + it.copy( + selectedPaymentMethodId = null, + errorMessage = app.getString(R.string.po_dynamic_checkout_error_method_unavailable) + ) } - } - _state.value.pendingSubmitPaymentMethodId?.let { id -> - _state.update { it.copy(pendingSubmitPaymentMethodId = null) } - paymentMethod(id)?.let { submit(it) } - .orElse { - _state.update { - it.copy(errorMessage = app.getString(R.string.po_dynamic_checkout_error_method_unavailable)) - } + } + } + _state.value.pendingSubmitPaymentMethodId?.let { id -> + _state.update { it.copy(pendingSubmitPaymentMethodId = null) } + paymentMethod(id)?.let { submit(it) } + .orElse { + _state.update { + it.copy(errorMessage = app.getString(R.string.po_dynamic_checkout_error_method_unavailable)) } - } + } } } From a5ee330fa8ba1490417e1650bc3c4074fbbf06d5 Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Mon, 21 Oct 2024 20:48:37 +0300 Subject: [PATCH 07/37] WillStart & DidStart --- .../processout/sdk/ui/checkout/DynamicCheckoutInteractor.kt | 6 +++++- 1 file changed, 5 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 b498a2e6..0eba3edd 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 @@ -13,7 +13,7 @@ import com.processout.sdk.api.dispatcher.POEventDispatcher import com.processout.sdk.api.dispatcher.card.tokenization.PODefaultCardTokenizationEventDispatcher import com.processout.sdk.api.dispatcher.napm.PODefaultNativeAlternativePaymentMethodEventDispatcher import com.processout.sdk.api.model.event.PODynamicCheckoutEvent -import com.processout.sdk.api.model.event.PODynamicCheckoutEvent.DidFail +import com.processout.sdk.api.model.event.PODynamicCheckoutEvent.* import com.processout.sdk.api.model.event.PONativeAlternativePaymentMethodEvent.WillSubmitParameters import com.processout.sdk.api.model.request.* import com.processout.sdk.api.model.response.* @@ -107,7 +107,11 @@ internal class DynamicCheckoutInteractor( init { interactorScope.launch { + POLogger.info("Starting dynamic checkout.") + dispatch(WillStart) start() + POLogger.info("Started: waiting for user input.") + dispatch(DidStart) } } From 3e8d11cc0ddf04959ca1157e027db57a992085da Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Mon, 21 Oct 2024 21:25:16 +0300 Subject: [PATCH 08/37] WillSelectPaymentMethod & extracted handleInternalFailure() function --- .../ui/checkout/DynamicCheckoutInteractor.kt | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 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 0eba3edd..10b68924 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 @@ -434,6 +434,12 @@ internal class DynamicCheckoutInteractor( if (event.id == _state.value.selectedPaymentMethodId) { return } + val originalPaymentMethod = originalPaymentMethod(event.id) + if (originalPaymentMethod == null) { + handleInternalFailure("Failed to select payment method: it is null.") + return + } + dispatch(WillSelectPaymentMethod(paymentMethod = originalPaymentMethod)) paymentMethod(event.id)?.let { paymentMethod -> if (_state.value.processingPaymentMethodId != null) { invalidateInvoice( @@ -637,6 +643,14 @@ internal class DynamicCheckoutInteractor( } } + private fun handleInternalFailure(message: String) { + invalidateInvoice( + reason = PODynamicCheckoutInvoiceInvalidationReason.Failure( + failure = ProcessOutResult.Failure(code = Internal(), message = message) + ) + ) + } + private fun invalidateInvoice(reason: PODynamicCheckoutInvoiceInvalidationReason) { interactorScope.launch { _state.update { it.copy(isInvoiceValid = false) } @@ -698,26 +712,12 @@ internal class DynamicCheckoutInteractor( ) { val paymentMethodId = _state.value.processingPaymentMethodId if (paymentMethodId == null) { - invalidateInvoice( - reason = PODynamicCheckoutInvoiceInvalidationReason.Failure( - failure = ProcessOutResult.Failure( - code = Internal(), - message = "Failed to authorize invoice: payment method ID is null." - ) - ) - ) + handleInternalFailure("Failed to authorize invoice: payment method ID is null.") return } val paymentMethod = originalPaymentMethod(paymentMethodId) if (paymentMethod == null) { - invalidateInvoice( - reason = PODynamicCheckoutInvoiceInvalidationReason.Failure( - failure = ProcessOutResult.Failure( - code = Internal(), - message = "Failed to authorize invoice: payment method is null." - ) - ) - ) + handleInternalFailure("Failed to authorize invoice: payment method is null.") return } interactorScope.launch { From 4416c40f95cf2b2da737cf5164e76d46c2f292e2 Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Tue, 22 Oct 2024 13:04:26 +0300 Subject: [PATCH 09/37] resetPaymentMethods() --- .../sdk/ui/checkout/DynamicCheckoutInteractor.kt | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 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 10b68924..3afd9555 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 @@ -158,12 +158,16 @@ internal class DynamicCheckoutInteractor( private fun reset(state: DynamicCheckoutInteractorState) { interactorScope.coroutineContext.cancelChildren() latestInvoiceRequest = null - cardTokenization.reset() - nativeAlternativePayment.reset() + resetPaymentMethods() _completion.update { Awaiting } _state.update { state } } + private fun resetPaymentMethods() { + cardTokenization.reset() + nativeAlternativePayment.reset() + } + private fun initState() = DynamicCheckoutInteractorState( loading = true, invoice = POInvoice(id = configuration.invoiceRequest.invoiceId), @@ -446,8 +450,7 @@ internal class DynamicCheckoutInteractor( reason = PODynamicCheckoutInvoiceInvalidationReason.PaymentMethodChanged ) } - cardTokenization.reset() - nativeAlternativePayment.reset() + resetPaymentMethods() if (_state.value.isInvoiceValid) { start(paymentMethod) } @@ -548,8 +551,7 @@ internal class DynamicCheckoutInteractor( return } if (paymentMethod.isExpress()) { - cardTokenization.reset() - nativeAlternativePayment.reset() + resetPaymentMethods() _state.update { it.copy( selectedPaymentMethodId = null, From e4c948c7f9aa5580f7ae1296ee1ce57e1c609e9a Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Tue, 22 Oct 2024 13:45:17 +0300 Subject: [PATCH 10/37] Updated handleCompletions() --- .../ui/checkout/DynamicCheckoutInteractor.kt | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 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 3afd9555..4229bd04 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,7 +118,6 @@ internal class DynamicCheckoutInteractor( private suspend fun start() { handleCompletions() dispatchEvents() - dispatchFailure() collectInvoice() collectInvoiceAuthorizationRequest() collectAuthorizeInvoiceResult() @@ -767,6 +766,17 @@ internal class DynamicCheckoutInteractor( } private fun handleCompletions() { + interactorScope.launch { + _completion.collect { completion -> + when (completion) { + Success -> { + // TODO + } + is Failure -> dispatch(DidFail(completion.failure)) + else -> {} + } + } + } interactorScope.launch { cardTokenization.completion.collect { completion -> when (completion) { @@ -820,16 +830,6 @@ internal class DynamicCheckoutInteractor( } } - private fun dispatchFailure() { - interactorScope.launch { - _completion.collect { - if (it is Failure) { - dispatch(DidFail(it.failure)) - } - } - } - } - private fun cancel() { _completion.update { Failure( From 7bfd22cb97343b02a41295fbf432a0c5ef95d163 Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Tue, 22 Oct 2024 15:10:42 +0300 Subject: [PATCH 11/37] Added an mapped 'original' to PaymentMethod interactor state --- .../sdk/ui/checkout/DynamicCheckoutInteractor.kt | 6 ++++++ .../sdk/ui/checkout/DynamicCheckoutInteractorState.kt | 7 +++++++ 2 files changed, 13 insertions(+) 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 4229bd04..52e2550c 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 @@ -251,6 +251,7 @@ internal class DynamicCheckoutInteractor( when (paymentMethod) { is PODynamicCheckoutPaymentMethod.Card -> Card( id = PaymentMethodId.CARD, + original = paymentMethod, configuration = paymentMethod.configuration, display = paymentMethod.display ) @@ -264,6 +265,7 @@ internal class DynamicCheckoutInteractor( if (googlePayService.isReadyToPay(isReadyToPayRequest)) GooglePay( id = paymentMethod.configuration.gatewayMerchantId, + original = paymentMethod, allowedPaymentMethods = POGooglePayRequestBuilder .allowedPaymentMethods(configuration.card) .toString(), @@ -275,6 +277,7 @@ internal class DynamicCheckoutInteractor( if (redirectUrl != null) { AlternativePayment( id = paymentMethod.configuration.gatewayConfigurationId, + original = paymentMethod, redirectUrl = redirectUrl, display = paymentMethod.display, isExpress = paymentMethod.flow == express @@ -282,6 +285,7 @@ internal class DynamicCheckoutInteractor( } else { NativeAlternativePayment( id = paymentMethod.configuration.gatewayConfigurationId, + original = paymentMethod, gatewayConfigurationId = paymentMethod.configuration.gatewayConfigurationId, display = paymentMethod.display ) @@ -289,12 +293,14 @@ internal class DynamicCheckoutInteractor( } is CardCustomerToken -> CustomerToken( id = paymentMethod.configuration.customerTokenId, + original = paymentMethod, configuration = paymentMethod.configuration, display = paymentMethod.display, isExpress = paymentMethod.flow == express ) is AlternativePaymentCustomerToken -> CustomerToken( id = paymentMethod.configuration.customerTokenId, + original = paymentMethod, configuration = paymentMethod.configuration, display = paymentMethod.display, isExpress = paymentMethod.flow == express 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 50c37c62..23e4fc8e 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 @@ -1,5 +1,6 @@ package com.processout.sdk.ui.checkout +import com.processout.sdk.api.model.response.PODynamicCheckoutPaymentMethod import com.processout.sdk.api.model.response.PODynamicCheckoutPaymentMethod.* import com.processout.sdk.api.model.response.POInvoice import org.json.JSONObject @@ -21,21 +22,25 @@ internal data class DynamicCheckoutInteractorState( sealed interface PaymentMethod { val id: String + val original: PODynamicCheckoutPaymentMethod data class Card( override val id: String, + override val original: PODynamicCheckoutPaymentMethod, val configuration: CardConfiguration, val display: Display ) : PaymentMethod data class GooglePay( override val id: String, + override val original: PODynamicCheckoutPaymentMethod, val allowedPaymentMethods: String, val paymentDataRequest: JSONObject ) : PaymentMethod data class AlternativePayment( override val id: String, + override val original: PODynamicCheckoutPaymentMethod, val redirectUrl: String, val display: Display, val isExpress: Boolean @@ -43,12 +48,14 @@ internal data class DynamicCheckoutInteractorState( data class NativeAlternativePayment( override val id: String, + override val original: PODynamicCheckoutPaymentMethod, val gatewayConfigurationId: String, val display: Display ) : PaymentMethod data class CustomerToken( override val id: String, + override val original: PODynamicCheckoutPaymentMethod, val configuration: CustomerTokenConfiguration, val display: Display, val isExpress: Boolean From 7ae096d0ccdc128ec38138aae9799325c8c4ac8a Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Tue, 22 Oct 2024 15:23:06 +0300 Subject: [PATCH 12/37] Unknown -> null --- .../com/processout/sdk/ui/checkout/DynamicCheckoutInteractor.kt | 2 +- 1 file changed, 1 insertion(+), 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 52e2550c..9976f5c8 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 @@ -305,7 +305,7 @@ internal class DynamicCheckoutInteractor( display = paymentMethod.display, isExpress = paymentMethod.flow == express ) - else -> null + Unknown -> null } } From 5bb41bf70689a6ae1c56adc235a0e7fd7d1ae830 Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Tue, 22 Oct 2024 15:43:51 +0300 Subject: [PATCH 13/37] Remove originalPaymentMethod() fun --- .../ui/checkout/DynamicCheckoutInteractor.kt | 18 +++--------------- 1 file changed, 3 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 9976f5c8..078200d6 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 @@ -407,18 +407,6 @@ internal class DynamicCheckoutInteractor( paymentMethod(it) } - private fun originalPaymentMethod(id: String): PODynamicCheckoutPaymentMethod? = - _state.value.invoice.paymentMethods?.find { - when (it) { - is PODynamicCheckoutPaymentMethod.Card -> PaymentMethodId.CARD == id - is PODynamicCheckoutPaymentMethod.GooglePay -> it.configuration.gatewayMerchantId == id - is PODynamicCheckoutPaymentMethod.AlternativePayment -> it.configuration.gatewayConfigurationId == id - is AlternativePaymentCustomerToken -> it.configuration.customerTokenId == id - is CardCustomerToken -> it.configuration.customerTokenId == id - Unknown -> false - } - } - fun onEvent(event: DynamicCheckoutEvent) { when (event) { is PaymentMethodSelected -> onPaymentMethodSelected(event) @@ -443,7 +431,7 @@ internal class DynamicCheckoutInteractor( if (event.id == _state.value.selectedPaymentMethodId) { return } - val originalPaymentMethod = originalPaymentMethod(event.id) + val originalPaymentMethod = paymentMethod(event.id)?.original if (originalPaymentMethod == null) { handleInternalFailure("Failed to select payment method: it is null.") return @@ -722,7 +710,7 @@ internal class DynamicCheckoutInteractor( handleInternalFailure("Failed to authorize invoice: payment method ID is null.") return } - val paymentMethod = originalPaymentMethod(paymentMethodId) + val paymentMethod = paymentMethod(paymentMethodId) if (paymentMethod == null) { handleInternalFailure("Failed to authorize invoice: payment method is null.") return @@ -736,7 +724,7 @@ internal class DynamicCheckoutInteractor( allowFallbackToSale = allowFallbackToSale, clientSecret = clientSecret ), - paymentMethod = paymentMethod + paymentMethod = paymentMethod.original ) eventDispatcher.send(request) } From 5bdb59d5c5100110cb14d0e4bc9d6b9e91af31ab Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Tue, 22 Oct 2024 16:10:28 +0300 Subject: [PATCH 14/37] pendingSubmitPaymentMethodId -> pendingSubmitPaymentMethod --- .../processout/sdk/ui/checkout/DynamicCheckoutInteractor.kt | 6 +++--- .../sdk/ui/checkout/DynamicCheckoutInteractorState.kt | 2 +- 2 files changed, 4 insertions(+), 4 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 078200d6..5dc48438 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 @@ -233,8 +233,8 @@ internal class DynamicCheckoutInteractor( } } } - _state.value.pendingSubmitPaymentMethodId?.let { id -> - _state.update { it.copy(pendingSubmitPaymentMethodId = null) } + _state.value.pendingSubmitPaymentMethod?.id?.let { id -> + _state.update { it.copy(pendingSubmitPaymentMethod = null) } paymentMethod(id)?.let { submit(it) } .orElse { _state.update { @@ -553,7 +553,7 @@ internal class DynamicCheckoutInteractor( } } if (_state.value.processingPaymentMethodId != null) { - _state.update { it.copy(pendingSubmitPaymentMethodId = paymentMethod.id) } + _state.update { it.copy(pendingSubmitPaymentMethod = paymentMethod) } invalidateInvoice( reason = PODynamicCheckoutInvoiceInvalidationReason.PaymentMethodChanged ) 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 23e4fc8e..92112ee0 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 @@ -14,7 +14,7 @@ internal data class DynamicCheckoutInteractorState( val cancelActionId: String, val selectedPaymentMethodId: String? = null, val processingPaymentMethodId: String? = null, - val pendingSubmitPaymentMethodId: String? = null, + val pendingSubmitPaymentMethod: PaymentMethod? = null, val errorMessage: String? = null, val delayedSuccess: Boolean = false ) { From 055f739ccdf75978c44482dab00e63305e4a51b2 Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Tue, 22 Oct 2024 16:40:20 +0300 Subject: [PATCH 15/37] selectedPaymentMethodId -> selectedPaymentMethod --- .../ui/checkout/DynamicCheckoutInteractor.kt | 34 ++++++++----------- .../DynamicCheckoutInteractorState.kt | 2 +- .../ui/checkout/DynamicCheckoutViewModel.kt | 8 ++--- 3 files changed, 17 insertions(+), 27 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 5dc48438..54e8eea3 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 @@ -131,11 +131,11 @@ internal class DynamicCheckoutInteractor( ) { configuration = configuration.copy(invoiceRequest = invoiceRequest) logAttributes = logAttributes(invoiceId = invoiceRequest.invoiceId) - val selectedPaymentMethodId = when (reason) { + val selectedPaymentMethod = when (reason) { is PODynamicCheckoutInvoiceInvalidationReason.Failure -> if (reason.failure.code == Cancelled) - _state.value.selectedPaymentMethodId else null - else -> _state.value.selectedPaymentMethodId + _state.value.selectedPaymentMethod else null + else -> _state.value.selectedPaymentMethod } val errorMessage = when (reason) { is PODynamicCheckoutInvoiceInvalidationReason.Failure -> @@ -146,7 +146,7 @@ internal class DynamicCheckoutInteractor( reset( state = _state.value.copy( invoice = POInvoice(id = configuration.invoiceRequest.invoiceId), - selectedPaymentMethodId = selectedPaymentMethodId, + selectedPaymentMethod = selectedPaymentMethod, processingPaymentMethodId = null, errorMessage = errorMessage ) @@ -222,12 +222,12 @@ internal class DynamicCheckoutInteractor( paymentMethods = mappedPaymentMethods ) } - _state.value.selectedPaymentMethodId?.let { id -> + _state.value.selectedPaymentMethod?.id?.let { id -> paymentMethod(id)?.let { start(it) } .orElse { _state.update { it.copy( - selectedPaymentMethodId = null, + selectedPaymentMethod = null, errorMessage = app.getString(R.string.po_dynamic_checkout_error_method_unavailable) ) } @@ -402,11 +402,6 @@ internal class DynamicCheckoutInteractor( private fun paymentMethod(id: String): PaymentMethod? = _state.value.paymentMethods.find { it.id == id } - private fun selectedPaymentMethod(): PaymentMethod? = - _state.value.selectedPaymentMethodId?.let { - paymentMethod(it) - } - fun onEvent(event: DynamicCheckoutEvent) { when (event) { is PaymentMethodSelected -> onPaymentMethodSelected(event) @@ -428,7 +423,7 @@ internal class DynamicCheckoutInteractor( } private fun onPaymentMethodSelected(event: PaymentMethodSelected) { - if (event.id == _state.value.selectedPaymentMethodId) { + if (event.id == _state.value.selectedPaymentMethod?.id) { return } val originalPaymentMethod = paymentMethod(event.id)?.original @@ -449,7 +444,7 @@ internal class DynamicCheckoutInteractor( } _state.update { it.copy( - selectedPaymentMethodId = event.id, + selectedPaymentMethod = paymentMethod, errorMessage = null ) } @@ -547,7 +542,7 @@ internal class DynamicCheckoutInteractor( resetPaymentMethods() _state.update { it.copy( - selectedPaymentMethodId = null, + selectedPaymentMethod = null, errorMessage = null ) } @@ -689,7 +684,7 @@ internal class DynamicCheckoutInteractor( private fun collectTokenizedCard() { interactorScope.launch { cardTokenizationEventDispatcher.processTokenizedCardRequest.collect { request -> - _state.update { it.copy(processingPaymentMethodId = selectedPaymentMethod()?.id) } + _state.update { it.copy(processingPaymentMethodId = _state.value.selectedPaymentMethod?.id) } authorizeInvoice( source = request.card.id, saveSource = request.saveCard, @@ -744,10 +739,9 @@ internal class DynamicCheckoutInteractor( private fun collectAuthorizeInvoiceResult() { interactorScope.launch { invoicesService.authorizeInvoiceResult.collect { result -> - if (selectedPaymentMethod() is Card) { - cardTokenizationEventDispatcher.complete(result) - } else { - result.onSuccess { + when (_state.value.selectedPaymentMethod) { + is Card -> cardTokenizationEventDispatcher.complete(result) + else -> result.onSuccess { handleSuccess() }.onFailure { failure -> invalidateInvoice( @@ -817,7 +811,7 @@ internal class DynamicCheckoutInteractor( interactorScope.launch { nativeAlternativePaymentEventDispatcher.events.collect { event -> if (event is WillSubmitParameters) { - _state.update { it.copy(processingPaymentMethodId = selectedPaymentMethod()?.id) } + _state.update { it.copy(processingPaymentMethodId = _state.value.selectedPaymentMethod?.id) } } eventDispatcher.send(event) } 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 92112ee0..b0db6f8a 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 @@ -12,7 +12,7 @@ internal data class DynamicCheckoutInteractorState( val paymentMethods: List, val submitActionId: String, val cancelActionId: String, - val selectedPaymentMethodId: String? = null, + val selectedPaymentMethod: PaymentMethod? = null, val processingPaymentMethodId: String? = null, val pendingSubmitPaymentMethod: PaymentMethod? = null, val errorMessage: String? = 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 5c09f19b..039f685d 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 @@ -17,7 +17,6 @@ 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 -import com.processout.sdk.ui.checkout.DynamicCheckoutInteractorState.PaymentMethod import com.processout.sdk.ui.checkout.DynamicCheckoutInteractorState.PaymentMethod.* import com.processout.sdk.ui.checkout.DynamicCheckoutViewModelState.* import com.processout.sdk.ui.checkout.DynamicCheckoutViewModelState.RegularPayment.Content @@ -124,9 +123,6 @@ internal class DynamicCheckoutViewModel private constructor( } } - private fun DynamicCheckoutInteractorState.selectedPaymentMethod(): PaymentMethod? = - paymentMethods.find { it.id == selectedPaymentMethodId } - private fun cancelAction( interactorState: DynamicCheckoutInteractorState, cardTokenizationState: CardTokenizationViewModelState, @@ -135,7 +131,7 @@ internal class DynamicCheckoutViewModel private constructor( val defaultText = app.getString(R.string.po_dynamic_checkout_button_cancel) val defaultCancelAction = configuration.cancelButton?.toActionState(interactorState, defaultText) val defaultCancelActionText = defaultCancelAction?.text ?: defaultText - return when (interactorState.selectedPaymentMethod()) { + return when (interactorState.selectedPaymentMethod) { is Card -> cardTokenizationState.secondaryAction?.copy( text = defaultCancelActionText, confirmation = defaultCancelAction?.confirmation @@ -238,7 +234,7 @@ internal class DynamicCheckoutViewModel private constructor( ): POImmutableList = interactorState.paymentMethods.mapNotNull { paymentMethod -> val id = paymentMethod.id - val selected = id == interactorState.selectedPaymentMethodId + val selected = id == interactorState.selectedPaymentMethod?.id val submitButtonText = configuration.submitButtonText ?: app.getString(R.string.po_dynamic_checkout_button_pay) when (paymentMethod) { is Card -> RegularPayment( From b91f6d8bca5bd07bf285345af1bb2c8452a74f22 Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Tue, 22 Oct 2024 17:29:48 +0300 Subject: [PATCH 16/37] selectedPaymentMethodId -> selectedPaymentMethod --- .../ui/checkout/DynamicCheckoutInteractor.kt | 58 ++++++++++--------- .../DynamicCheckoutInteractorState.kt | 2 +- .../ui/checkout/DynamicCheckoutViewModel.kt | 8 +-- 3 files changed, 35 insertions(+), 33 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 54e8eea3..7603d5a3 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 @@ -147,7 +147,7 @@ internal class DynamicCheckoutInteractor( state = _state.value.copy( invoice = POInvoice(id = configuration.invoiceRequest.invoiceId), selectedPaymentMethod = selectedPaymentMethod, - processingPaymentMethodId = null, + processingPaymentMethod = null, errorMessage = errorMessage ) ) @@ -433,7 +433,7 @@ internal class DynamicCheckoutInteractor( } dispatch(WillSelectPaymentMethod(paymentMethod = originalPaymentMethod)) paymentMethod(event.id)?.let { paymentMethod -> - if (_state.value.processingPaymentMethodId != null) { + if (_state.value.processingPaymentMethod != null) { invalidateInvoice( reason = PODynamicCheckoutInvoiceInvalidationReason.PaymentMethodChanged ) @@ -535,7 +535,7 @@ internal class DynamicCheckoutInteractor( } private fun submit(paymentMethod: PaymentMethod) { - if (paymentMethod.id == _state.value.processingPaymentMethodId) { + if (paymentMethod.id == _state.value.processingPaymentMethod?.id) { return } if (paymentMethod.isExpress()) { @@ -547,7 +547,7 @@ internal class DynamicCheckoutInteractor( ) } } - if (_state.value.processingPaymentMethodId != null) { + if (_state.value.processingPaymentMethod != null) { _state.update { it.copy(pendingSubmitPaymentMethod = paymentMethod) } invalidateInvoice( reason = PODynamicCheckoutInvoiceInvalidationReason.PaymentMethodChanged @@ -555,40 +555,42 @@ internal class DynamicCheckoutInteractor( return } when (paymentMethod) { - is GooglePay -> { + is GooglePay -> interactorScope.launch { - _state.update { it.copy(processingPaymentMethodId = paymentMethod.id) } + _state.update { it.copy(processingPaymentMethod = paymentMethod) } _submitEvents.send( DynamicCheckoutSubmitEvent.GooglePay( paymentDataRequest = paymentMethod.paymentDataRequest ) ) } - } - is AlternativePayment -> submitAlternativePayment( - id = paymentMethod.id, - redirectUrl = paymentMethod.redirectUrl - ) - is CustomerToken -> { - val redirectUrl = paymentMethod.configuration.redirectUrl - if (redirectUrl != null) { - submitAlternativePayment( - id = paymentMethod.id, - redirectUrl = redirectUrl - ) + is AlternativePayment -> submitAlternativePayment(paymentMethod) + is CustomerToken -> + if (paymentMethod.configuration.redirectUrl != null) { + submitAlternativePayment(paymentMethod) } else { - _state.update { it.copy(processingPaymentMethodId = paymentMethod.id) } + _state.update { it.copy(processingPaymentMethod = paymentMethod) } authorizeInvoice(source = paymentMethod.configuration.customerTokenId) } - } else -> {} } } - private fun submitAlternativePayment( - id: String, - redirectUrl: String - ) { + private fun submitAlternativePayment(paymentMethod: PaymentMethod) { + val redirectUrl = when (paymentMethod) { + is AlternativePayment -> paymentMethod.redirectUrl + is CustomerToken -> paymentMethod.configuration.redirectUrl + else -> null + } + if (redirectUrl.isNullOrBlank()) { + handleAlternativePayment( + ProcessOutResult.Failure( + code = Generic(), + message = "Missing redirect URL in alternative payment configuration." + ) + ) + return + } val returnUrl = configuration.alternativePayment.returnUrl if (returnUrl.isNullOrBlank()) { handleAlternativePayment( @@ -600,7 +602,7 @@ internal class DynamicCheckoutInteractor( return } interactorScope.launch { - _state.update { it.copy(processingPaymentMethodId = id) } + _state.update { it.copy(processingPaymentMethod = paymentMethod) } _submitEvents.send( DynamicCheckoutSubmitEvent.AlternativePayment( redirectUrl = redirectUrl, @@ -684,7 +686,7 @@ internal class DynamicCheckoutInteractor( private fun collectTokenizedCard() { interactorScope.launch { cardTokenizationEventDispatcher.processTokenizedCardRequest.collect { request -> - _state.update { it.copy(processingPaymentMethodId = _state.value.selectedPaymentMethod?.id) } + _state.update { it.copy(processingPaymentMethod = _state.value.selectedPaymentMethod) } authorizeInvoice( source = request.card.id, saveSource = request.saveCard, @@ -700,7 +702,7 @@ internal class DynamicCheckoutInteractor( allowFallbackToSale: Boolean = false, clientSecret: String? = null ) { - val paymentMethodId = _state.value.processingPaymentMethodId + val paymentMethodId = _state.value.processingPaymentMethod?.id if (paymentMethodId == null) { handleInternalFailure("Failed to authorize invoice: payment method ID is null.") return @@ -811,7 +813,7 @@ internal class DynamicCheckoutInteractor( interactorScope.launch { nativeAlternativePaymentEventDispatcher.events.collect { event -> if (event is WillSubmitParameters) { - _state.update { it.copy(processingPaymentMethodId = _state.value.selectedPaymentMethod?.id) } + _state.update { it.copy(processingPaymentMethod = _state.value.selectedPaymentMethod) } } eventDispatcher.send(event) } 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 b0db6f8a..52d0d72a 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 @@ -13,7 +13,7 @@ internal data class DynamicCheckoutInteractorState( val submitActionId: String, val cancelActionId: String, val selectedPaymentMethod: PaymentMethod? = null, - val processingPaymentMethodId: String? = null, + val processingPaymentMethod: PaymentMethod? = null, val pendingSubmitPaymentMethod: PaymentMethod? = null, val errorMessage: String? = null, val delayedSuccess: Boolean = false 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 039f685d..c69bcc4e 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 @@ -166,7 +166,7 @@ internal class DynamicCheckoutViewModel private constructor( id = interactorState.cancelActionId, text = text ?: defaultText, primary = false, - enabled = interactorState.processingPaymentMethodId == null, + enabled = interactorState.processingPaymentMethod == null, confirmation = confirmation?.map() ) @@ -192,7 +192,7 @@ internal class DynamicCheckoutViewModel private constructor( id = interactorState.submitActionId, text = String(), primary = true, - enabled = id != interactorState.processingPaymentMethodId + enabled = id != interactorState.processingPaymentMethod?.id ) ) is AlternativePayment -> if (paymentMethod.isExpress) @@ -223,7 +223,7 @@ internal class DynamicCheckoutViewModel private constructor( id = interactorState.submitActionId, text = display.name, primary = true, - enabled = id != interactorState.processingPaymentMethodId + enabled = id != interactorState.processingPaymentMethod?.id ) ) @@ -261,7 +261,7 @@ internal class DynamicCheckoutViewModel private constructor( id = interactorState.submitActionId, text = submitButtonText, primary = true, - loading = interactorState.processingPaymentMethodId != null + loading = interactorState.processingPaymentMethod != null ) ) else null is NativeAlternativePayment -> RegularPayment( From 2bb43d3b4ad74371e9df1ed814f81a3ce0c3a2fc Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Tue, 22 Oct 2024 18:23:13 +0300 Subject: [PATCH 17/37] description with fallback on name for customer token types (card and apm) --- .../com/processout/sdk/api/model/response/InvoiceResponse.kt | 3 ++- .../processout/sdk/ui/checkout/DynamicCheckoutViewModel.kt | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/sdk/src/main/kotlin/com/processout/sdk/api/model/response/InvoiceResponse.kt b/sdk/src/main/kotlin/com/processout/sdk/api/model/response/InvoiceResponse.kt index a731827a..b22848d4 100644 --- a/sdk/src/main/kotlin/com/processout/sdk/api/model/response/InvoiceResponse.kt +++ b/sdk/src/main/kotlin/com/processout/sdk/api/model/response/InvoiceResponse.kt @@ -140,7 +140,8 @@ sealed class PODynamicCheckoutPaymentMethod { val name: String, val logo: POImageResource, @Json(name = "brand_color") - val brandColor: POColor + val brandColor: POColor, + val description: 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 c69bcc4e..d6016e28 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 @@ -198,12 +198,14 @@ internal class DynamicCheckoutViewModel private constructor( is AlternativePayment -> if (paymentMethod.isExpress) expressPayment( id = id, + text = paymentMethod.display.name, display = paymentMethod.display, interactorState = interactorState ) else null is CustomerToken -> if (paymentMethod.isExpress) expressPayment( id = id, + text = paymentMethod.display.description ?: paymentMethod.display.name, display = paymentMethod.display, interactorState = interactorState ) else null @@ -213,6 +215,7 @@ internal class DynamicCheckoutViewModel private constructor( private fun expressPayment( id: String, + text: String, display: Display, interactorState: DynamicCheckoutInteractorState ) = ExpressPayment.Express( @@ -221,7 +224,7 @@ internal class DynamicCheckoutViewModel private constructor( brandColor = display.brandColor, submitAction = POActionState( id = interactorState.submitActionId, - text = display.name, + text = text, primary = true, enabled = id != interactorState.processingPaymentMethod?.id ) From ba177e8f20773e3846dd5a54ddd54cea3404261d Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Tue, 22 Oct 2024 18:29:58 +0300 Subject: [PATCH 18/37] Fix authorizeInvoice() --- .../sdk/ui/checkout/DynamicCheckoutInteractor.kt | 7 +------ 1 file changed, 1 insertion(+), 6 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 7603d5a3..cac64cd1 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 @@ -702,12 +702,7 @@ internal class DynamicCheckoutInteractor( allowFallbackToSale: Boolean = false, clientSecret: String? = null ) { - val paymentMethodId = _state.value.processingPaymentMethod?.id - if (paymentMethodId == null) { - handleInternalFailure("Failed to authorize invoice: payment method ID is null.") - return - } - val paymentMethod = paymentMethod(paymentMethodId) + val paymentMethod = _state.value.processingPaymentMethod if (paymentMethod == null) { handleInternalFailure("Failed to authorize invoice: payment method is null.") return From a7e05d2725eadf469183d8747e67b947341120a7 Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Tue, 22 Oct 2024 18:38:10 +0300 Subject: [PATCH 19/37] Dispatch DidCompletePayment --- .../processout/sdk/ui/checkout/DynamicCheckoutInteractor.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 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 cac64cd1..dc57c14f 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 @@ -754,8 +754,8 @@ internal class DynamicCheckoutInteractor( interactorScope.launch { _completion.collect { completion -> when (completion) { - Success -> { - // TODO + Success -> _state.value.processingPaymentMethod?.let { paymentMethod -> + dispatch(DidCompletePayment(paymentMethod.original)) } is Failure -> dispatch(DidFail(completion.failure)) else -> {} From a4e44553fe9295ebf1680ee4002011f29863bd2c Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Wed, 23 Oct 2024 13:24:10 +0300 Subject: [PATCH 20/37] DidRequestCancelConfirmation --- .../processout/sdk/api/model/event/PODynamicCheckoutEvent.kt | 5 +++++ .../processout/sdk/ui/checkout/DynamicCheckoutInteractor.kt | 5 ++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/sdk/src/main/kotlin/com/processout/sdk/api/model/event/PODynamicCheckoutEvent.kt b/sdk/src/main/kotlin/com/processout/sdk/api/model/event/PODynamicCheckoutEvent.kt index e2106517..6766ccf8 100644 --- a/sdk/src/main/kotlin/com/processout/sdk/api/model/event/PODynamicCheckoutEvent.kt +++ b/sdk/src/main/kotlin/com/processout/sdk/api/model/event/PODynamicCheckoutEvent.kt @@ -41,6 +41,11 @@ sealed class PODynamicCheckoutEvent { val paymentMethod: PODynamicCheckoutPaymentMethod ) : PODynamicCheckoutEvent() + /** + * Event is sent when user asked to confirm cancellation, e.g. via dialog. + */ + data object DidRequestCancelConfirmation : PODynamicCheckoutEvent() + /** * Event is sent after payment was confirmed to be captured. This is a final event. */ 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 dc57c14f..3985feaf 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 @@ -409,7 +409,10 @@ internal class DynamicCheckoutInteractor( is FieldFocusChanged -> onFieldFocusChanged(event) is Action -> onAction(event) is ActionConfirmationRequested -> { - // TODO + POLogger.debug("Requested the user to confirm the action: %s", event.id) + if (event.id == ActionId.CANCEL) { + dispatch(DidRequestCancelConfirmation) + } } is Dismiss -> { if (_state.value.delayedSuccess) { From 3a19a6bf215ea828a137b877b491ca162a51e91b Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Wed, 23 Oct 2024 14:59:13 +0300 Subject: [PATCH 21/37] Refactored onPaymentMethodSelected() --- .../sdk/ui/checkout/DynamicCheckoutInteractor.kt | 13 ++++--------- 1 file changed, 4 insertions(+), 9 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 3985feaf..b714344b 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 @@ -429,20 +429,15 @@ internal class DynamicCheckoutInteractor( if (event.id == _state.value.selectedPaymentMethod?.id) { return } - val originalPaymentMethod = paymentMethod(event.id)?.original - if (originalPaymentMethod == null) { - handleInternalFailure("Failed to select payment method: it is null.") - return - } - dispatch(WillSelectPaymentMethod(paymentMethod = originalPaymentMethod)) paymentMethod(event.id)?.let { paymentMethod -> + dispatch(WillSelectPaymentMethod(paymentMethod = paymentMethod.original)) + resetPaymentMethods() if (_state.value.processingPaymentMethod != null) { invalidateInvoice( reason = PODynamicCheckoutInvoiceInvalidationReason.PaymentMethodChanged ) - } - resetPaymentMethods() - if (_state.value.isInvoiceValid) { + } else { + dispatch(DidSelectPaymentMethod(paymentMethod = paymentMethod.original)) start(paymentMethod) } _state.update { From 69f152df3a206aaaeb8cc8c8e8e4566cc385ce2a Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Wed, 23 Oct 2024 18:18:17 +0300 Subject: [PATCH 22/37] DidSelectPaymentMethod --- .../api/model/event/PODynamicCheckoutEvent.kt | 7 ---- .../ui/checkout/DynamicCheckoutInteractor.kt | 32 +++++++++++++------ 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/sdk/src/main/kotlin/com/processout/sdk/api/model/event/PODynamicCheckoutEvent.kt b/sdk/src/main/kotlin/com/processout/sdk/api/model/event/PODynamicCheckoutEvent.kt index 6766ccf8..18ddb9ca 100644 --- a/sdk/src/main/kotlin/com/processout/sdk/api/model/event/PODynamicCheckoutEvent.kt +++ b/sdk/src/main/kotlin/com/processout/sdk/api/model/event/PODynamicCheckoutEvent.kt @@ -19,13 +19,6 @@ sealed class PODynamicCheckoutEvent { */ data object DidStart : PODynamicCheckoutEvent() - /** - * Event is sent when user attempts to select payment method. - */ - data class WillSelectPaymentMethod( - val paymentMethod: PODynamicCheckoutPaymentMethod - ) : PODynamicCheckoutEvent() - /** * Event is sent when payment method is selected by the user. */ 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 b714344b..97a7043c 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 @@ -235,12 +235,16 @@ internal class DynamicCheckoutInteractor( } _state.value.pendingSubmitPaymentMethod?.id?.let { id -> _state.update { it.copy(pendingSubmitPaymentMethod = null) } - paymentMethod(id)?.let { submit(it) } - .orElse { - _state.update { - it.copy(errorMessage = app.getString(R.string.po_dynamic_checkout_error_method_unavailable)) - } + paymentMethod(id)?.let { + submit( + paymentMethod = it, + dispatchEvents = false + ) + }.orElse { + _state.update { + it.copy(errorMessage = app.getString(R.string.po_dynamic_checkout_error_method_unavailable)) } + } } } @@ -430,14 +434,13 @@ internal class DynamicCheckoutInteractor( return } paymentMethod(event.id)?.let { paymentMethod -> - dispatch(WillSelectPaymentMethod(paymentMethod = paymentMethod.original)) + dispatch(DidSelectPaymentMethod(paymentMethod = paymentMethod.original)) resetPaymentMethods() if (_state.value.processingPaymentMethod != null) { invalidateInvoice( reason = PODynamicCheckoutInvoiceInvalidationReason.PaymentMethodChanged ) } else { - dispatch(DidSelectPaymentMethod(paymentMethod = paymentMethod.original)) start(paymentMethod) } _state.update { @@ -510,7 +513,12 @@ internal class DynamicCheckoutInteractor( private fun onAction(event: Action) { val paymentMethod = event.paymentMethodId?.let { paymentMethod(it) } when (event.actionId) { - ActionId.SUBMIT -> paymentMethod?.let { submit(it) } + ActionId.SUBMIT -> paymentMethod?.let { + submit( + paymentMethod = it, + dispatchEvents = true + ) + } ActionId.CANCEL -> cancel() else -> when (paymentMethod) { is Card -> cardTokenization.onEvent( @@ -532,11 +540,17 @@ internal class DynamicCheckoutInteractor( is CustomerToken -> isExpress } - private fun submit(paymentMethod: PaymentMethod) { + private fun submit( + paymentMethod: PaymentMethod, + dispatchEvents: Boolean + ) { if (paymentMethod.id == _state.value.processingPaymentMethod?.id) { return } if (paymentMethod.isExpress()) { + if (dispatchEvents) { + dispatch(DidSelectPaymentMethod(paymentMethod = paymentMethod.original)) + } resetPaymentMethods() _state.update { it.copy( From 0a769f78dcba573bd65bad1cae5ae823628007e0 Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Wed, 23 Oct 2024 18:22:08 +0300 Subject: [PATCH 23/37] handle invoice auth failure --- .../ui/checkout/DynamicCheckoutInteractor.kt | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 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 97a7043c..399f69e5 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 @@ -647,14 +647,6 @@ internal class DynamicCheckoutInteractor( } } - private fun handleInternalFailure(message: String) { - invalidateInvoice( - reason = PODynamicCheckoutInvoiceInvalidationReason.Failure( - failure = ProcessOutResult.Failure(code = Internal(), message = message) - ) - ) - } - private fun invalidateInvoice(reason: PODynamicCheckoutInvoiceInvalidationReason) { interactorScope.launch { _state.update { it.copy(isInvoiceValid = false) } @@ -716,7 +708,14 @@ internal class DynamicCheckoutInteractor( ) { val paymentMethod = _state.value.processingPaymentMethod if (paymentMethod == null) { - handleInternalFailure("Failed to authorize invoice: payment method is null.") + invalidateInvoice( + reason = PODynamicCheckoutInvoiceInvalidationReason.Failure( + failure = ProcessOutResult.Failure( + code = Internal(), + message = "Failed to authorize invoice: payment method is null." + ) + ) + ) return } interactorScope.launch { From 8fa7aa9fb1f5667a93f05dcd4eba81c80172c3b6 Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Wed, 23 Oct 2024 18:28:38 +0300 Subject: [PATCH 24/37] Extract restoreSelectedPaymentMethod() and handlePendingSubmit() functions --- .../sdk/ui/checkout/DynamicCheckoutInteractor.kt | 8 ++++++++ 1 file changed, 8 insertions(+) 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 399f69e5..c9fe1368 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 @@ -222,6 +222,11 @@ internal class DynamicCheckoutInteractor( paymentMethods = mappedPaymentMethods ) } + restoreSelectedPaymentMethod() + handlePendingSubmit() + } + + private fun restoreSelectedPaymentMethod() { _state.value.selectedPaymentMethod?.id?.let { id -> paymentMethod(id)?.let { start(it) } .orElse { @@ -233,6 +238,9 @@ internal class DynamicCheckoutInteractor( } } } + } + + private fun handlePendingSubmit() { _state.value.pendingSubmitPaymentMethod?.id?.let { id -> _state.update { it.copy(pendingSubmitPaymentMethod = null) } paymentMethod(id)?.let { From 71c944832f7415b7b15839538e50629f2700a072 Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Wed, 23 Oct 2024 18:36:12 +0300 Subject: [PATCH 25/37] Define DidFailPayment event --- .../sdk/api/model/event/PODynamicCheckoutEvent.kt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/sdk/src/main/kotlin/com/processout/sdk/api/model/event/PODynamicCheckoutEvent.kt b/sdk/src/main/kotlin/com/processout/sdk/api/model/event/PODynamicCheckoutEvent.kt index 18ddb9ca..6a333b78 100644 --- a/sdk/src/main/kotlin/com/processout/sdk/api/model/event/PODynamicCheckoutEvent.kt +++ b/sdk/src/main/kotlin/com/processout/sdk/api/model/event/PODynamicCheckoutEvent.kt @@ -19,6 +19,11 @@ sealed class PODynamicCheckoutEvent { */ data object DidStart : PODynamicCheckoutEvent() + /** + * Event is sent when user asked to confirm cancellation, e.g. via dialog. + */ + data object DidRequestCancelConfirmation : PODynamicCheckoutEvent() + /** * Event is sent when payment method is selected by the user. */ @@ -29,16 +34,11 @@ sealed class PODynamicCheckoutEvent { /** * Event is sent when payment method selection has failed. */ - data class DidFailToSelectPaymentMethod( + data class DidFailPayment( val failure: ProcessOutResult.Failure, val paymentMethod: PODynamicCheckoutPaymentMethod ) : PODynamicCheckoutEvent() - /** - * Event is sent when user asked to confirm cancellation, e.g. via dialog. - */ - data object DidRequestCancelConfirmation : PODynamicCheckoutEvent() - /** * Event is sent after payment was confirmed to be captured. This is a final event. */ From 2df0fa51fba3b2c2b2f5cd1a36244956ea9a6d38 Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Wed, 23 Oct 2024 19:37:45 +0300 Subject: [PATCH 26/37] Dispatch DidFailPayment event --- .../sdk/ui/checkout/DynamicCheckoutInteractor.kt | 12 ++++++++++++ 1 file changed, 12 insertions(+) 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 c9fe1368..d20bc145 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 @@ -131,6 +131,17 @@ internal class DynamicCheckoutInteractor( ) { configuration = configuration.copy(invoiceRequest = invoiceRequest) logAttributes = logAttributes(invoiceId = invoiceRequest.invoiceId) + val didFailPaymentEvent: DidFailPayment? = + if (reason is PODynamicCheckoutInvoiceInvalidationReason.Failure) { + with(_state.value) { + processingPaymentMethod ?: selectedPaymentMethod + }?.let { paymentMethod -> + DidFailPayment( + failure = reason.failure, + paymentMethod = paymentMethod.original + ) + } + } else null val selectedPaymentMethod = when (reason) { is PODynamicCheckoutInvoiceInvalidationReason.Failure -> if (reason.failure.code == Cancelled) @@ -151,6 +162,7 @@ internal class DynamicCheckoutInteractor( errorMessage = errorMessage ) ) + didFailPaymentEvent?.let { dispatch(it) } interactorScope.launch { start() } } From b7f25f079fb613544eb230e6d9115971cd7dcebc Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Thu, 24 Oct 2024 13:20:11 +0300 Subject: [PATCH 27/37] Refactor restart() fun in interactor --- .../ui/checkout/DynamicCheckoutInteractor.kt | 51 +++++++++++-------- 1 file changed, 29 insertions(+), 22 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 d20bc145..b2d18984 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 @@ -131,32 +131,39 @@ internal class DynamicCheckoutInteractor( ) { configuration = configuration.copy(invoiceRequest = invoiceRequest) logAttributes = logAttributes(invoiceId = invoiceRequest.invoiceId) - val didFailPaymentEvent: DidFailPayment? = - if (reason is PODynamicCheckoutInvoiceInvalidationReason.Failure) { - with(_state.value) { - processingPaymentMethod ?: selectedPaymentMethod - }?.let { paymentMethod -> - DidFailPayment( - failure = reason.failure, - paymentMethod = paymentMethod.original - ) + var didFailPaymentEvent: DidFailPayment? = null + val selectedPaymentMethod: PaymentMethod? + val errorMessage: String? + with(_state.value) { + when (reason) { + is PODynamicCheckoutInvoiceInvalidationReason.Failure -> { + val paymentMethod = processingPaymentMethod ?: this.selectedPaymentMethod + if (paymentMethod != null) { + didFailPaymentEvent = DidFailPayment( + failure = reason.failure, + paymentMethod = paymentMethod.original + ) + } + when (reason.failure.code) { + Cancelled -> { + selectedPaymentMethod = this.selectedPaymentMethod + errorMessage = null + } + else -> { + selectedPaymentMethod = null + errorMessage = app.getString(R.string.po_dynamic_checkout_error_generic) + } + } } - } else null - val selectedPaymentMethod = when (reason) { - is PODynamicCheckoutInvoiceInvalidationReason.Failure -> - if (reason.failure.code == Cancelled) - _state.value.selectedPaymentMethod else null - else -> _state.value.selectedPaymentMethod - } - val errorMessage = when (reason) { - is PODynamicCheckoutInvoiceInvalidationReason.Failure -> - if (reason.failure.code == Cancelled) null - else app.getString(R.string.po_dynamic_checkout_error_generic) - else -> null + PODynamicCheckoutInvoiceInvalidationReason.PaymentMethodChanged -> { + selectedPaymentMethod = this.selectedPaymentMethod + errorMessage = null + } + } } reset( state = _state.value.copy( - invoice = POInvoice(id = configuration.invoiceRequest.invoiceId), + invoice = POInvoice(id = invoiceRequest.invoiceId), selectedPaymentMethod = selectedPaymentMethod, processingPaymentMethod = null, errorMessage = errorMessage From 1cc37e2ab25a1e4d3a6ef979db853b2ef666bbf9 Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Thu, 24 Oct 2024 14:11:40 +0300 Subject: [PATCH 28/37] authorizeInvoice with invoiceId from config request --- .../com/processout/sdk/ui/checkout/DynamicCheckoutInteractor.kt | 2 +- 1 file changed, 1 insertion(+), 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 b2d18984..33618bfb 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 @@ -748,7 +748,7 @@ internal class DynamicCheckoutInteractor( interactorScope.launch { val request = PODynamicCheckoutInvoiceAuthorizationRequest( request = POInvoiceAuthorizationRequest( - invoiceId = _state.value.invoice.id, + invoiceId = configuration.invoiceRequest.invoiceId, source = source, saveSource = saveSource, allowFallbackToSale = allowFallbackToSale, From d610eb3a0e95e8e5a4b47a6959b6dc8441a60c90 Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Thu, 24 Oct 2024 14:15:24 +0300 Subject: [PATCH 29/37] Move initState() to the top --- .../ui/checkout/DynamicCheckoutInteractor.kt | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 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 33618bfb..58015cd5 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 @@ -115,6 +115,15 @@ internal class DynamicCheckoutInteractor( } } + private fun initState() = DynamicCheckoutInteractorState( + loading = true, + invoice = POInvoice(id = configuration.invoiceRequest.invoiceId), + isInvoiceValid = false, + paymentMethods = emptyList(), + submitActionId = ActionId.SUBMIT, + cancelActionId = ActionId.CANCEL + ) + private suspend fun start() { handleCompletions() dispatchEvents() @@ -186,15 +195,6 @@ internal class DynamicCheckoutInteractor( nativeAlternativePayment.reset() } - private fun initState() = DynamicCheckoutInteractorState( - loading = true, - invoice = POInvoice(id = configuration.invoiceRequest.invoiceId), - isInvoiceValid = false, - paymentMethods = emptyList(), - submitActionId = ActionId.SUBMIT, - cancelActionId = ActionId.CANCEL - ) - private suspend fun fetchConfiguration() { invoicesService.invoice(configuration.invoiceRequest) .onSuccess { invoice -> From 7e65d4351825f8989a9621c582adff5505657bc2 Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Thu, 24 Oct 2024 16:25:14 +0300 Subject: [PATCH 30/37] submitGooglePay() --- .../ui/checkout/DynamicCheckoutInteractor.kt | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 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 58015cd5..3b7c18ff 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 @@ -594,15 +594,7 @@ internal class DynamicCheckoutInteractor( return } when (paymentMethod) { - is GooglePay -> - interactorScope.launch { - _state.update { it.copy(processingPaymentMethod = paymentMethod) } - _submitEvents.send( - DynamicCheckoutSubmitEvent.GooglePay( - paymentDataRequest = paymentMethod.paymentDataRequest - ) - ) - } + is GooglePay -> submitGooglePay(paymentMethod) is AlternativePayment -> submitAlternativePayment(paymentMethod) is CustomerToken -> if (paymentMethod.configuration.redirectUrl != null) { @@ -615,6 +607,17 @@ internal class DynamicCheckoutInteractor( } } + private fun submitGooglePay(paymentMethod: GooglePay) { + interactorScope.launch { + _state.update { it.copy(processingPaymentMethod = paymentMethod) } + _submitEvents.send( + DynamicCheckoutSubmitEvent.GooglePay( + paymentDataRequest = paymentMethod.paymentDataRequest + ) + ) + } + } + private fun submitAlternativePayment(paymentMethod: PaymentMethod) { val redirectUrl = when (paymentMethod) { is AlternativePayment -> paymentMethod.redirectUrl From 57382bd084f0ef58892a2d67387078e9479a1cb1 Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Thu, 24 Oct 2024 16:29:50 +0300 Subject: [PATCH 31/37] submitCustomerToken() --- .../ui/checkout/DynamicCheckoutInteractor.kt | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 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 3b7c18ff..7756ba94 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 @@ -596,13 +596,7 @@ internal class DynamicCheckoutInteractor( when (paymentMethod) { is GooglePay -> submitGooglePay(paymentMethod) is AlternativePayment -> submitAlternativePayment(paymentMethod) - is CustomerToken -> - if (paymentMethod.configuration.redirectUrl != null) { - submitAlternativePayment(paymentMethod) - } else { - _state.update { it.copy(processingPaymentMethod = paymentMethod) } - authorizeInvoice(source = paymentMethod.configuration.customerTokenId) - } + is CustomerToken -> submitCustomerToken(paymentMethod) else -> {} } } @@ -654,6 +648,15 @@ internal class DynamicCheckoutInteractor( } } + private fun submitCustomerToken(paymentMethod: CustomerToken) { + if (paymentMethod.configuration.redirectUrl != null) { + submitAlternativePayment(paymentMethod) + } else { + _state.update { it.copy(processingPaymentMethod = paymentMethod) } + authorizeInvoice(source = paymentMethod.configuration.customerTokenId) + } + } + fun handleGooglePay(result: ProcessOutResult) { result.onSuccess { response -> authorizeInvoice(source = response.card.id) From a3388969001f1014cb60c83b0929548e6ab34e0a Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Thu, 24 Oct 2024 16:39:28 +0300 Subject: [PATCH 32/37] onActionConfirmationRequested() --- .../sdk/ui/checkout/DynamicCheckoutInteractor.kt | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 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 7756ba94..40561512 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 @@ -439,12 +439,7 @@ internal class DynamicCheckoutInteractor( is FieldValueChanged -> onFieldValueChanged(event) is FieldFocusChanged -> onFieldFocusChanged(event) is Action -> onAction(event) - is ActionConfirmationRequested -> { - POLogger.debug("Requested the user to confirm the action: %s", event.id) - if (event.id == ActionId.CANCEL) { - dispatch(DidRequestCancelConfirmation) - } - } + is ActionConfirmationRequested -> onActionConfirmationRequested(event) is Dismiss -> { if (_state.value.delayedSuccess) { _completion.update { Success } @@ -559,6 +554,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 PaymentMethod.isExpress(): Boolean = when (this) { is Card, is NativeAlternativePayment -> false From 7bb5e21e3e24d83b40d55fb634a205d36aa47cdf Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Thu, 24 Oct 2024 16:48:24 +0300 Subject: [PATCH 33/37] dismiss() --- .../ui/checkout/DynamicCheckoutInteractor.kt | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 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 40561512..ba575462 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 @@ -440,14 +440,7 @@ internal class DynamicCheckoutInteractor( is FieldFocusChanged -> onFieldFocusChanged(event) is Action -> onAction(event) is ActionConfirmationRequested -> onActionConfirmationRequested(event) - is Dismiss -> { - if (_state.value.delayedSuccess) { - _completion.update { Success } - } else { - POLogger.warn("Dismissed: %s", event.failure) - _completion.update { Failure(event.failure) } - } - } + is Dismiss -> dismiss(event) } } @@ -872,6 +865,15 @@ internal class DynamicCheckoutInteractor( } } + private fun dismiss(event: Dismiss) { + if (_state.value.delayedSuccess) { + _completion.update { Success } + } else { + POLogger.warn("Dismissed: %s", event.failure) + _completion.update { Failure(event.failure) } + } + } + fun onCleared() { threeDSService.close() handler.removeCallbacksAndMessages(null) From 53deb7518b10610771d07751c64bfd9d647ddbe2 Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Thu, 24 Oct 2024 16:53:37 +0300 Subject: [PATCH 34/37] Move paymentMethod() fun --- .../processout/sdk/ui/checkout/DynamicCheckoutInteractor.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 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 ba575462..130a45ce 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 @@ -430,9 +430,6 @@ internal class DynamicCheckoutInteractor( //endregion - private fun paymentMethod(id: String): PaymentMethod? = - _state.value.paymentMethods.find { it.id == id } - fun onEvent(event: DynamicCheckoutEvent) { when (event) { is PaymentMethodSelected -> onPaymentMethodSelected(event) @@ -444,6 +441,9 @@ internal class DynamicCheckoutInteractor( } } + private fun paymentMethod(id: String): PaymentMethod? = + _state.value.paymentMethods.find { it.id == id } + private fun onPaymentMethodSelected(event: PaymentMethodSelected) { if (event.id == _state.value.selectedPaymentMethod?.id) { return From d9af9c59ebf70d18655b205c93b7e64fff4617a7 Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Thu, 24 Oct 2024 17:43:27 +0300 Subject: [PATCH 35/37] nAPM default values on DC --- ...ternativePaymentMethodDefaultValuesRequest.kt | 5 +++-- ...ernativePaymentMethodDefaultValuesResponse.kt | 5 +++-- .../sdk/ui/checkout/DynamicCheckoutInteractor.kt | 16 ++++++++++++++++ .../sdk/ui/checkout/PODynamicCheckoutDelegate.kt | 5 +++++ .../sdk/ui/checkout/PODynamicCheckoutLauncher.kt | 13 +++++++++++++ 5 files changed, 40 insertions(+), 4 deletions(-) diff --git a/sdk/src/main/kotlin/com/processout/sdk/api/model/request/PONativeAlternativePaymentMethodDefaultValuesRequest.kt b/sdk/src/main/kotlin/com/processout/sdk/api/model/request/PONativeAlternativePaymentMethodDefaultValuesRequest.kt index 8143aab4..62744c9e 100644 --- a/sdk/src/main/kotlin/com/processout/sdk/api/model/request/PONativeAlternativePaymentMethodDefaultValuesRequest.kt +++ b/sdk/src/main/kotlin/com/processout/sdk/api/model/request/PONativeAlternativePaymentMethodDefaultValuesRequest.kt @@ -1,5 +1,6 @@ package com.processout.sdk.api.model.request +import com.processout.sdk.api.dispatcher.POEventDispatcher import com.processout.sdk.api.model.response.PONativeAlternativePaymentMethodParameter import com.processout.sdk.core.annotation.ProcessOutInternalApi import java.util.UUID @@ -16,5 +17,5 @@ data class PONativeAlternativePaymentMethodDefaultValuesRequest @ProcessOutInter val gatewayConfigurationId: String, val invoiceId: String, val parameters: List, - val uuid: UUID = UUID.randomUUID() -) + override val uuid: UUID = UUID.randomUUID() +) : POEventDispatcher.Request diff --git a/sdk/src/main/kotlin/com/processout/sdk/api/model/response/PONativeAlternativePaymentMethodDefaultValuesResponse.kt b/sdk/src/main/kotlin/com/processout/sdk/api/model/response/PONativeAlternativePaymentMethodDefaultValuesResponse.kt index d5895596..e572545b 100644 --- a/sdk/src/main/kotlin/com/processout/sdk/api/model/response/PONativeAlternativePaymentMethodDefaultValuesResponse.kt +++ b/sdk/src/main/kotlin/com/processout/sdk/api/model/response/PONativeAlternativePaymentMethodDefaultValuesResponse.kt @@ -1,5 +1,6 @@ package com.processout.sdk.api.model.response +import com.processout.sdk.api.dispatcher.POEventDispatcher import com.processout.sdk.api.model.request.PONativeAlternativePaymentMethodDefaultValuesRequest import java.util.UUID @@ -11,9 +12,9 @@ import java.util.UUID * @param[defaultValues] Map where key is [PONativeAlternativePaymentMethodParameter.key] and value is a default value for this parameter. */ data class PONativeAlternativePaymentMethodDefaultValuesResponse internal constructor( - val uuid: UUID, + override val uuid: UUID, val defaultValues: Map -) +) : POEventDispatcher.Response /** * Creates response with default values from request to use the same UUID. 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 130a45ce..24755a07 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 @@ -131,6 +131,7 @@ internal class DynamicCheckoutInteractor( collectInvoiceAuthorizationRequest() collectAuthorizeInvoiceResult() collectTokenizedCard() + collectDefaultValues() fetchConfiguration() } @@ -852,6 +853,21 @@ internal class DynamicCheckoutInteractor( eventDispatcher.send(event) } } + interactorScope.launch { + nativeAlternativePaymentEventDispatcher.defaultValuesRequest.collect { request -> + eventDispatcher.send(request) + } + } + } + + private fun collectDefaultValues() { + eventDispatcher.subscribeForResponse( + coroutineScope = interactorScope + ) { response -> + interactorScope.launch { + nativeAlternativePaymentEventDispatcher.provideDefaultValues(response) + } + } } private fun cancel() { diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/checkout/PODynamicCheckoutDelegate.kt b/ui/src/main/kotlin/com/processout/sdk/ui/checkout/PODynamicCheckoutDelegate.kt index 7a8f3cb9..cbb3f91c 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/checkout/PODynamicCheckoutDelegate.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/checkout/PODynamicCheckoutDelegate.kt @@ -6,6 +6,7 @@ import com.processout.sdk.api.model.event.PONativeAlternativePaymentMethodEvent import com.processout.sdk.api.model.request.PODynamicCheckoutInvoiceInvalidationReason import com.processout.sdk.api.model.request.POInvoiceAuthorizationRequest import com.processout.sdk.api.model.request.POInvoiceRequest +import com.processout.sdk.api.model.request.PONativeAlternativePaymentMethodDefaultValuesRequest import com.processout.sdk.api.model.response.PODynamicCheckoutPaymentMethod import com.processout.sdk.api.model.response.POInvoice import com.processout.sdk.ui.core.annotation.ProcessOutInternalApi @@ -36,4 +37,8 @@ interface PODynamicCheckoutDelegate { request: POInvoiceAuthorizationRequest, paymentMethod: PODynamicCheckoutPaymentMethod ): POInvoiceAuthorizationRequest = request + + suspend fun defaultValues( + request: PONativeAlternativePaymentMethodDefaultValuesRequest + ): Map = emptyMap() } 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 9df040c7..385c131b 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 @@ -12,6 +12,7 @@ import com.processout.sdk.api.model.event.PODynamicCheckoutEvent import com.processout.sdk.api.model.event.PONativeAlternativePaymentMethodEvent import com.processout.sdk.api.model.request.PODynamicCheckoutInvoiceAuthorizationRequest import com.processout.sdk.api.model.request.PODynamicCheckoutInvoiceRequest +import com.processout.sdk.api.model.request.PONativeAlternativePaymentMethodDefaultValuesRequest import com.processout.sdk.api.model.response.toResponse import com.processout.sdk.api.service.PO3DSService import com.processout.sdk.api.service.proxy3ds.POProxy3DSServiceRequest @@ -89,6 +90,7 @@ class PODynamicCheckoutLauncher private constructor( dispatchEvents() dispatchInvoice() dispatchInvoiceAuthorizationRequest() + dispatchDefaultValues() dispatch3DSService() } @@ -132,6 +134,17 @@ class PODynamicCheckoutLauncher private constructor( } } + private fun dispatchDefaultValues() { + eventDispatcher.subscribeForRequest( + coroutineScope = scope + ) { request -> + scope.launch { + val defaultValues = delegate.defaultValues(request) + eventDispatcher.send(request.toResponse(defaultValues)) + } + } + } + private fun dispatch3DSService() { eventDispatcher.subscribeForRequest( coroutineScope = scope From 7314ce77cdddbdb23648c8f9b01fe7f1713fdada Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Thu, 24 Oct 2024 18:40:22 +0300 Subject: [PATCH 36/37] card preferred scheme on DC --- .../POCardTokenizationPreferredSchemeRequest.kt | 5 +++-- .../POCardTokenizationPreferredSchemeResponse.kt | 5 +++-- .../sdk/ui/checkout/DynamicCheckoutInteractor.kt | 16 ++++++++++++++++ .../sdk/ui/checkout/PODynamicCheckoutDelegate.kt | 9 +++++---- .../sdk/ui/checkout/PODynamicCheckoutLauncher.kt | 13 +++++++++++++ 5 files changed, 40 insertions(+), 8 deletions(-) diff --git a/sdk/src/main/kotlin/com/processout/sdk/api/model/request/POCardTokenizationPreferredSchemeRequest.kt b/sdk/src/main/kotlin/com/processout/sdk/api/model/request/POCardTokenizationPreferredSchemeRequest.kt index 002036f9..2d989dba 100644 --- a/sdk/src/main/kotlin/com/processout/sdk/api/model/request/POCardTokenizationPreferredSchemeRequest.kt +++ b/sdk/src/main/kotlin/com/processout/sdk/api/model/request/POCardTokenizationPreferredSchemeRequest.kt @@ -1,5 +1,6 @@ package com.processout.sdk.api.model.request +import com.processout.sdk.api.dispatcher.POEventDispatcher import com.processout.sdk.api.model.response.POCardIssuerInformation import com.processout.sdk.core.annotation.ProcessOutInternalApi import java.util.UUID @@ -12,5 +13,5 @@ import java.util.UUID */ data class POCardTokenizationPreferredSchemeRequest @ProcessOutInternalApi constructor( val issuerInformation: POCardIssuerInformation, - val uuid: UUID = UUID.randomUUID() -) + override val uuid: UUID = UUID.randomUUID() +) : POEventDispatcher.Request diff --git a/sdk/src/main/kotlin/com/processout/sdk/api/model/response/POCardTokenizationPreferredSchemeResponse.kt b/sdk/src/main/kotlin/com/processout/sdk/api/model/response/POCardTokenizationPreferredSchemeResponse.kt index 5a2e23af..e3e5fe19 100644 --- a/sdk/src/main/kotlin/com/processout/sdk/api/model/response/POCardTokenizationPreferredSchemeResponse.kt +++ b/sdk/src/main/kotlin/com/processout/sdk/api/model/response/POCardTokenizationPreferredSchemeResponse.kt @@ -1,5 +1,6 @@ package com.processout.sdk.api.model.response +import com.processout.sdk.api.dispatcher.POEventDispatcher import com.processout.sdk.api.model.request.POCardTokenizationPreferredSchemeRequest import java.util.UUID @@ -12,10 +13,10 @@ import java.util.UUID * @param[preferredScheme] Preferred scheme that will be used by default for card tokenization. */ data class POCardTokenizationPreferredSchemeResponse internal constructor( - val uuid: UUID, + override val uuid: UUID, val issuerInformation: POCardIssuerInformation, val preferredScheme: String? -) +) : POEventDispatcher.Response /** * Creates [POCardTokenizationPreferredSchemeResponse] from [POCardTokenizationPreferredSchemeRequest]. 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 24755a07..55c06a01 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 @@ -131,6 +131,7 @@ internal class DynamicCheckoutInteractor( collectInvoiceAuthorizationRequest() collectAuthorizeInvoiceResult() collectTokenizedCard() + collectPreferredScheme() collectDefaultValues() fetchConfiguration() } @@ -845,6 +846,11 @@ internal class DynamicCheckoutInteractor( interactorScope.launch { cardTokenizationEventDispatcher.events.collect { eventDispatcher.send(it) } } + interactorScope.launch { + cardTokenizationEventDispatcher.preferredSchemeRequest.collect { request -> + eventDispatcher.send(request) + } + } interactorScope.launch { nativeAlternativePaymentEventDispatcher.events.collect { event -> if (event is WillSubmitParameters) { @@ -860,6 +866,16 @@ internal class DynamicCheckoutInteractor( } } + private fun collectPreferredScheme() { + eventDispatcher.subscribeForResponse( + coroutineScope = interactorScope + ) { response -> + interactorScope.launch { + cardTokenizationEventDispatcher.preferredScheme(response) + } + } + } + private fun collectDefaultValues() { eventDispatcher.subscribeForResponse( coroutineScope = interactorScope diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/checkout/PODynamicCheckoutDelegate.kt b/ui/src/main/kotlin/com/processout/sdk/ui/checkout/PODynamicCheckoutDelegate.kt index cbb3f91c..0f81ade2 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/checkout/PODynamicCheckoutDelegate.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/checkout/PODynamicCheckoutDelegate.kt @@ -3,10 +3,7 @@ package com.processout.sdk.ui.checkout import com.processout.sdk.api.model.event.POCardTokenizationEvent import com.processout.sdk.api.model.event.PODynamicCheckoutEvent import com.processout.sdk.api.model.event.PONativeAlternativePaymentMethodEvent -import com.processout.sdk.api.model.request.PODynamicCheckoutInvoiceInvalidationReason -import com.processout.sdk.api.model.request.POInvoiceAuthorizationRequest -import com.processout.sdk.api.model.request.POInvoiceRequest -import com.processout.sdk.api.model.request.PONativeAlternativePaymentMethodDefaultValuesRequest +import com.processout.sdk.api.model.request.* import com.processout.sdk.api.model.response.PODynamicCheckoutPaymentMethod import com.processout.sdk.api.model.response.POInvoice import com.processout.sdk.ui.core.annotation.ProcessOutInternalApi @@ -38,6 +35,10 @@ interface PODynamicCheckoutDelegate { paymentMethod: PODynamicCheckoutPaymentMethod ): POInvoiceAuthorizationRequest = request + suspend fun preferredScheme( + request: POCardTokenizationPreferredSchemeRequest + ): String? = null + suspend fun defaultValues( request: PONativeAlternativePaymentMethodDefaultValuesRequest ): Map = emptyMap() 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 385c131b..8046ec7a 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 @@ -10,6 +10,7 @@ import com.processout.sdk.api.dispatcher.POEventDispatcher import com.processout.sdk.api.model.event.POCardTokenizationEvent import com.processout.sdk.api.model.event.PODynamicCheckoutEvent import com.processout.sdk.api.model.event.PONativeAlternativePaymentMethodEvent +import com.processout.sdk.api.model.request.POCardTokenizationPreferredSchemeRequest import com.processout.sdk.api.model.request.PODynamicCheckoutInvoiceAuthorizationRequest import com.processout.sdk.api.model.request.PODynamicCheckoutInvoiceRequest import com.processout.sdk.api.model.request.PONativeAlternativePaymentMethodDefaultValuesRequest @@ -90,6 +91,7 @@ class PODynamicCheckoutLauncher private constructor( dispatchEvents() dispatchInvoice() dispatchInvoiceAuthorizationRequest() + dispatchPreferredScheme() dispatchDefaultValues() dispatch3DSService() } @@ -134,6 +136,17 @@ class PODynamicCheckoutLauncher private constructor( } } + private fun dispatchPreferredScheme() { + eventDispatcher.subscribeForRequest( + coroutineScope = scope + ) { request -> + scope.launch { + val preferredScheme = delegate.preferredScheme(request) + eventDispatcher.send(request.toResponse(preferredScheme)) + } + } + } + private fun dispatchDefaultValues() { eventDispatcher.subscribeForRequest( coroutineScope = scope From 41e2b98659d89805fd65dbaead8dfd8ec7553728 Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Thu, 24 Oct 2024 19:45:26 +0300 Subject: [PATCH 37/37] DidFailPayment KDoc --- .../processout/sdk/api/model/event/PODynamicCheckoutEvent.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sdk/src/main/kotlin/com/processout/sdk/api/model/event/PODynamicCheckoutEvent.kt b/sdk/src/main/kotlin/com/processout/sdk/api/model/event/PODynamicCheckoutEvent.kt index 6a333b78..ed82ad50 100644 --- a/sdk/src/main/kotlin/com/processout/sdk/api/model/event/PODynamicCheckoutEvent.kt +++ b/sdk/src/main/kotlin/com/processout/sdk/api/model/event/PODynamicCheckoutEvent.kt @@ -32,7 +32,8 @@ sealed class PODynamicCheckoutEvent { ) : PODynamicCheckoutEvent() /** - * Event is sent when payment method selection has failed. + * Event is sent when certain payment method has failed with retryable error. + * User can provide different payment details or try another payment method. */ data class DidFailPayment( val failure: ProcessOutResult.Failure,