Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,10 @@ import com.woocommerce.android.cardreader.internal.payments.PaymentManager
import com.woocommerce.android.cardreader.internal.payments.PaymentUtils
import com.woocommerce.android.cardreader.internal.payments.RefundErrorMapper
import com.woocommerce.android.cardreader.internal.payments.actions.CancelPaymentAction
import com.woocommerce.android.cardreader.internal.payments.actions.CollectInteracRefundAction
import com.woocommerce.android.cardreader.internal.payments.actions.CollectPaymentAction
import com.woocommerce.android.cardreader.internal.payments.actions.CreatePaymentAction
import com.woocommerce.android.cardreader.internal.payments.actions.ProcessInteracRefundAction
import com.woocommerce.android.cardreader.internal.payments.actions.ProcessPaymentAction
import com.woocommerce.android.cardreader.internal.payments.actions.ProcessRefundAction
import com.woocommerce.android.cardreader.internal.wrappers.PaymentIntentParametersFactory
import com.woocommerce.android.cardreader.internal.wrappers.PaymentMethodTypeMapper
import com.woocommerce.android.cardreader.internal.wrappers.TerminalWrapper
Expand Down Expand Up @@ -69,8 +68,7 @@ object CardReaderManagerFactory {
cardReaderConfigFactory
),
InteracRefundManager(
CollectInteracRefundAction(terminal),
ProcessInteracRefundAction(terminal),
ProcessRefundAction(terminal),
RefundErrorMapper(),
paymentUtils,
),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,57 +1,33 @@
package com.woocommerce.android.cardreader.internal.payments

import com.woocommerce.android.cardreader.internal.payments.actions.CollectInteracRefundAction
import com.woocommerce.android.cardreader.internal.payments.actions.ProcessInteracRefundAction
import com.woocommerce.android.cardreader.internal.payments.actions.ProcessRefundAction
import com.woocommerce.android.cardreader.payments.CardInteracRefundStatus
import com.woocommerce.android.cardreader.payments.RefundConfig
import com.woocommerce.android.cardreader.payments.RefundParams
import com.woocommerce.android.cardreader.payments.toStripeRefundConfiguration
import com.woocommerce.android.cardreader.payments.toStripeRefundParameters
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.flow.flow

internal class InteracRefundManager(
private val collectInteracRefundAction: CollectInteracRefundAction,
private val processInteracRefundAction: ProcessInteracRefundAction,
private val processRefundAction: ProcessRefundAction,
private val refundErrorMapper: RefundErrorMapper,
private val paymentsUtils: PaymentUtils,
) {
fun refundInteracPayment(
refundParameters: RefundParams,
refundConfig: RefundConfig,
): Flow<CardInteracRefundStatus> = flow {
collectInteracRefund(refundParameters, refundConfig)
}

private suspend fun FlowCollector<CardInteracRefundStatus>.collectInteracRefund(
refundParameters: RefundParams,
refundConfig: RefundConfig,
) {
emit(CardInteracRefundStatus.CollectingInteracRefund)
collectInteracRefundAction.collectRefund(
processRefundAction.processRefund(
refundParameters.toStripeRefundParameters(paymentsUtils),
refundConfig.toStripeRefundConfiguration()
).collect { refundStatus ->
when (refundStatus) {
CollectInteracRefundAction.CollectInteracRefundStatus.Success -> {
processInteracRefund(refundParameters)
}
is CollectInteracRefundAction.CollectInteracRefundStatus.Failure -> {
emit(refundErrorMapper.mapTerminalError(refundParameters, refundStatus.exception))
}
}
}
}

private suspend fun FlowCollector<CardInteracRefundStatus>.processInteracRefund(refundParameters: RefundParams) {
emit(CardInteracRefundStatus.ProcessingInteracRefund)
processInteracRefundAction.processRefund().collect { status ->
).collect { status ->
when (status) {
is ProcessInteracRefundAction.ProcessRefundStatus.Success -> {
is ProcessRefundAction.ProcessRefundStatus.Success -> {
emit(CardInteracRefundStatus.InteracRefundSuccess)
}
is ProcessInteracRefundAction.ProcessRefundStatus.Failure -> {
is ProcessRefundAction.ProcessRefundStatus.Failure -> {
emit(refundErrorMapper.mapTerminalError(refundParameters, status.exception))
}
}
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,36 +1,38 @@
package com.woocommerce.android.cardreader.internal.payments.actions

import com.stripe.stripeterminal.external.callable.Callback
import com.stripe.stripeterminal.external.callable.RefundCallback
import com.stripe.stripeterminal.external.models.CollectRefundConfiguration
import com.stripe.stripeterminal.external.models.Refund
import com.stripe.stripeterminal.external.models.RefundParameters
import com.stripe.stripeterminal.external.models.TerminalException
import com.woocommerce.android.cardreader.internal.wrappers.TerminalWrapper
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow

internal class CollectInteracRefundAction(private val terminal: TerminalWrapper) {
sealed class CollectInteracRefundStatus {
object Success : CollectInteracRefundStatus()
data class Failure(val exception: TerminalException) : CollectInteracRefundStatus()
internal class ProcessRefundAction(private val terminal: TerminalWrapper) {
sealed class ProcessRefundStatus {
data class Success(val refund: Refund) : ProcessRefundStatus()
data class Failure(val exception: TerminalException) : ProcessRefundStatus()
}

fun collectRefund(
fun processRefund(
refundParameters: RefundParameters,
refundConfiguration: CollectRefundConfiguration
): Flow<CollectInteracRefundStatus> {
): Flow<ProcessRefundStatus> {
return callbackFlow {
val cancelable = terminal.refundPayment(
val cancelable = terminal.processRefund(
refundParameters,
refundConfiguration,
object : Callback {
override fun onSuccess() {
trySend(CollectInteracRefundStatus.Success)
object : RefundCallback {
override fun onSuccess(refund: Refund) {
trySend(ProcessRefundStatus.Success(refund))
close()
}

override fun onFailure(e: TerminalException) {
trySend(CollectInteracRefundStatus.Failure(e))
trySend(ProcessRefundStatus.Failure(e))
close()
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,16 +87,11 @@ internal class TerminalWrapper {
fun cancelPayment(paymentIntent: PaymentIntent, callback: PaymentIntentCallback) =
Terminal.getInstance().cancelPaymentIntent(paymentIntent, callback)

@Suppress("DEPRECATION")
fun refundPayment(
fun processRefund(
refundParameters: RefundParameters,
refundConfiguration: CollectRefundConfiguration,
callback: Callback
) = Terminal.getInstance().collectRefundPaymentMethod(refundParameters, refundConfiguration, callback)

@Suppress("DEPRECATION")
fun processRefund(callback: RefundCallback) =
Terminal.getInstance().confirmRefund(callback)
callback: RefundCallback
): Cancelable = Terminal.getInstance().processRefund(refundParameters, refundConfiguration, callback)

fun installSoftwareUpdate() = Terminal.getInstance().installAvailableUpdate()

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
package com.woocommerce.android.cardreader.internal.payments

import com.woocommerce.android.cardreader.internal.CardReaderBaseUnitTest
import com.woocommerce.android.cardreader.internal.payments.actions.CollectInteracRefundAction
import com.woocommerce.android.cardreader.internal.payments.actions.ProcessInteracRefundAction
import com.woocommerce.android.cardreader.internal.payments.actions.ProcessRefundAction
import com.woocommerce.android.cardreader.payments.CardInteracRefundStatus
import com.woocommerce.android.cardreader.payments.CardInteracRefundStatus.RefundStatusErrorType.DeclinedByBackendError
import com.woocommerce.android.cardreader.payments.CardInteracRefundStatus.RefundStatusErrorType.Generic
Expand All @@ -24,10 +23,7 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.junit.MockitoJUnitRunner
import org.mockito.kotlin.any
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
import java.math.BigDecimal
import kotlin.reflect.KClass
Expand All @@ -43,22 +39,19 @@ private const val TIMEOUT = 1000L
class InteracRefundManagerTest : CardReaderBaseUnitTest() {
private lateinit var manager: InteracRefundManager

private val collectInteracRefundAction: CollectInteracRefundAction = mock()
private val processInteracRefundAction: ProcessInteracRefundAction = mock()
private val processRefundAction: ProcessRefundAction = mock()
private val refundErrorMapper: RefundErrorMapper = mock()
private val paymentsUtils: PaymentUtils = mock()

private val expectedInteracRefundSequence = listOf(
CardInteracRefundStatus.CollectingInteracRefund::class,
CardInteracRefundStatus.ProcessingInteracRefund::class,
CardInteracRefundStatus.InteracRefundSuccess::class
)

@Before
fun setUp() = testBlocking {
manager = InteracRefundManager(
collectInteracRefundAction,
processInteracRefundAction,
processRefundAction,
refundErrorMapper,
paymentsUtils,
)
Expand All @@ -73,34 +66,21 @@ class InteracRefundManagerTest : CardReaderBaseUnitTest() {
}

@Test
fun `given collect interac refund success, when refund starts, then ProcessingInteracRefund is emitted`() =
fun `given process refund success, when refund starts, then InteracRefundSuccess is emitted`() =
testBlocking {
whenever(collectInteracRefundAction.collectRefund(anyOrNull(), any()))
.thenReturn(flow { emit(CollectInteracRefundAction.CollectInteracRefundStatus.Success) })
whenever(processRefundAction.processRefund(any(), any()))
.thenReturn(flow { emit(ProcessRefundAction.ProcessRefundStatus.Success(mock())) })
val result = manager.refundInteracPayment(createRefundParams(), refundConfig)
.takeUntil(CardInteracRefundStatus.ProcessingInteracRefund::class).toList()
.takeUntil(CardInteracRefundStatus.InteracRefundSuccess::class).toList()

assertThat(result.last()).isInstanceOf(CardInteracRefundStatus.ProcessingInteracRefund::class.java)
assertThat(result.last()).isInstanceOf(CardInteracRefundStatus.InteracRefundSuccess::class.java)
}

@Test
fun `given collect interac refund failure, when refund starts, then ProcessingInteracRefund is NOT emitted`() =
fun `given process refund failure, when refund starts, then failure is emitted`() =
testBlocking {
whenever(collectInteracRefundAction.collectRefund(anyOrNull(), any()))
.thenReturn(flow { emit(CollectInteracRefundAction.CollectInteracRefundStatus.Failure(mock())) })
whenever(refundErrorMapper.mapTerminalError(any(), any()))
.thenReturn(CardInteracRefundStatus.InteracRefundFailure(Generic, "", mock()))
val result = manager.refundInteracPayment(createRefundParams(), refundConfig).toList()

assertThat(result.last()).isNotInstanceOf(CardInteracRefundStatus.ProcessingInteracRefund::class.java)
verify(processInteracRefundAction, never()).processRefund()
}

@Test
fun `given collect interac refund failure, when refund starts, then failure is emitted`() =
testBlocking {
whenever(collectInteracRefundAction.collectRefund(anyOrNull(), any()))
.thenReturn(flow { emit(CollectInteracRefundAction.CollectInteracRefundStatus.Failure(mock())) })
whenever(processRefundAction.processRefund(any(), any()))
.thenReturn(flow { emit(ProcessRefundAction.ProcessRefundStatus.Failure(mock())) })
whenever(refundErrorMapper.mapTerminalError(any(), any()))
.thenReturn(
CardInteracRefundStatus.InteracRefundFailure(Generic, "", mock())
Expand All @@ -111,11 +91,11 @@ class InteracRefundManagerTest : CardReaderBaseUnitTest() {
}

@Test
fun `given collect interac refund failure, when refund starts, then failure message is captured`() =
fun `given process refund failure, when refund starts, then failure message is captured`() =
testBlocking {
val expectedErrorMessage = "Generic Error"
whenever(collectInteracRefundAction.collectRefund(anyOrNull(), any()))
.thenReturn(flow { emit(CollectInteracRefundAction.CollectInteracRefundStatus.Failure(mock())) })
whenever(processRefundAction.processRefund(any(), any()))
.thenReturn(flow { emit(ProcessRefundAction.ProcessRefundStatus.Failure(mock())) })
whenever(refundErrorMapper.mapTerminalError(any(), any()))
.thenReturn(
CardInteracRefundStatus.InteracRefundFailure(Generic, expectedErrorMessage, mock())
Expand All @@ -129,11 +109,11 @@ class InteracRefundManagerTest : CardReaderBaseUnitTest() {
}

@Test
fun `given collect interac refund failure, when refund starts, then failure type is captured`() =
fun `given process refund failure, when refund starts, then failure type is captured`() =
testBlocking {
val expectedErrorType = DeclinedByBackendError.Unknown
whenever(collectInteracRefundAction.collectRefund(anyOrNull(), any()))
.thenReturn(flow { emit(CollectInteracRefundAction.CollectInteracRefundStatus.Failure(mock())) })
whenever(processRefundAction.processRefund(any(), any()))
.thenReturn(flow { emit(ProcessRefundAction.ProcessRefundStatus.Failure(mock())) })
whenever(refundErrorMapper.mapTerminalError(any(), any()))
.thenReturn(
CardInteracRefundStatus.InteracRefundFailure(expectedErrorType, "Declined", mock())
Expand All @@ -147,11 +127,11 @@ class InteracRefundManagerTest : CardReaderBaseUnitTest() {
}

@Test
fun `given collect interac refund failure, when refund starts, then refund params is captured`() =
fun `given process refund failure, when refund starts, then refund params is captured`() =
testBlocking {
val expectedRefundParams = createRefundParams()
whenever(collectInteracRefundAction.collectRefund(anyOrNull(), any()))
.thenReturn(flow { emit(CollectInteracRefundAction.CollectInteracRefundStatus.Failure(mock())) })
whenever(processRefundAction.processRefund(any(), any()))
.thenReturn(flow { emit(ProcessRefundAction.ProcessRefundStatus.Failure(mock())) })
whenever(refundErrorMapper.mapTerminalError(any(), any()))
.thenReturn(
CardInteracRefundStatus.InteracRefundFailure(
Expand All @@ -169,15 +149,15 @@ class InteracRefundManagerTest : CardReaderBaseUnitTest() {
}

@Test
fun `given collect interac refund failure, when refund starts, then flow terminates`() =
fun `given process refund failure, when refund starts, then flow terminates`() =
testBlocking {
whenever(collectInteracRefundAction.collectRefund(anyOrNull(), any()))
.thenReturn(flow { emit(CollectInteracRefundAction.CollectInteracRefundStatus.Failure(mock())) })
whenever(processRefundAction.processRefund(any(), any()))
.thenReturn(flow { emit(ProcessRefundAction.ProcessRefundStatus.Failure(mock())) })
val result = withTimeoutOrNull(TIMEOUT) {
manager.refundInteracPayment(createRefundParams(), refundConfig).toList()
}

assertThat(result).isNotNull // verify the flow did not timeout
assertThat(result).isNotNull
}

private fun <T> Flow<T>.takeUntil(untilStatus: KClass<*>): Flow<T> =
Expand Down
Loading