Skip to content

Commit

Permalink
Catch all errors coming from CustomerAdapter
Browse files Browse the repository at this point in the history
  • Loading branch information
samer-stripe committed Oct 4, 2024
1 parent a6a1bf7 commit 89099c9
Show file tree
Hide file tree
Showing 3 changed files with 203 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import com.stripe.android.paymentsheet.PaymentSheet
import com.stripe.android.paymentsheet.model.SavedSelection
import com.stripe.android.paymentsheet.repositories.ElementsSessionRepository
import kotlinx.coroutines.async
import kotlinx.coroutines.withContext
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.coroutines.CoroutineContext
Expand Down Expand Up @@ -66,37 +67,51 @@ internal class CustomerAdapterDataSource @Inject constructor(
}.toCustomerSheetDataResult()
}

override suspend fun retrievePaymentMethods(): CustomerSheetDataResult<List<PaymentMethod>> {
return customerAdapter.retrievePaymentMethods().toCustomerSheetDataResult()
override suspend fun retrievePaymentMethods() = withContext(workContext) {
runCatchingAdapterTask {
customerAdapter.retrievePaymentMethods()
}.toCustomerSheetDataResult()
}

override suspend fun updatePaymentMethod(
paymentMethodId: String,
params: PaymentMethodUpdateParams,
): CustomerSheetDataResult<PaymentMethod> {
return customerAdapter.updatePaymentMethod(paymentMethodId, params).toCustomerSheetDataResult()
) = withContext(workContext) {
runCatchingAdapterTask {
customerAdapter.updatePaymentMethod(paymentMethodId, params)
}.toCustomerSheetDataResult()
}

override suspend fun attachPaymentMethod(paymentMethodId: String): CustomerSheetDataResult<PaymentMethod> {
return customerAdapter.attachPaymentMethod(paymentMethodId).toCustomerSheetDataResult()
override suspend fun attachPaymentMethod(paymentMethodId: String) = withContext(workContext) {
runCatchingAdapterTask {
customerAdapter.attachPaymentMethod(paymentMethodId)
}.toCustomerSheetDataResult()
}

override suspend fun detachPaymentMethod(paymentMethodId: String): CustomerSheetDataResult<PaymentMethod> {
return customerAdapter.detachPaymentMethod(paymentMethodId).toCustomerSheetDataResult()
override suspend fun detachPaymentMethod(paymentMethodId: String) = withContext(workContext) {
runCatchingAdapterTask {
customerAdapter.detachPaymentMethod(paymentMethodId)
}.toCustomerSheetDataResult()
}

override suspend fun retrieveSavedSelection(): CustomerSheetDataResult<SavedSelection?> {
return customerAdapter.retrieveSelectedPaymentOption().map { result ->
result?.toSavedSelection()
override suspend fun retrieveSavedSelection() = withContext(workContext) {
runCatchingAdapterTask {
customerAdapter.retrieveSelectedPaymentOption().map { result ->
result?.toSavedSelection()
}
}.toCustomerSheetDataResult()
}

override suspend fun setSavedSelection(selection: SavedSelection?): CustomerSheetDataResult<Unit> {
return customerAdapter.setSelectedPaymentOption(selection?.toPaymentOption()).toCustomerSheetDataResult()
override suspend fun setSavedSelection(selection: SavedSelection?) = withContext(workContext) {
runCatchingAdapterTask {
customerAdapter.setSelectedPaymentOption(selection?.toPaymentOption())
}.toCustomerSheetDataResult()
}

override suspend fun retrieveSetupIntentClientSecret(): CustomerSheetDataResult<String> {
return customerAdapter.setupIntentClientSecretForCustomerAttach().toCustomerSheetDataResult()
override suspend fun retrieveSetupIntentClientSecret() = withContext(workContext) {
runCatchingAdapterTask {
customerAdapter.setupIntentClientSecretForCustomerAttach()
}.toCustomerSheetDataResult()
}

private suspend fun fetchElementsSession(): Result<ElementsSession> {
Expand Down Expand Up @@ -149,4 +164,17 @@ internal class CustomerAdapterDataSource @Inject constructor(
listOf("card")
}
}

private suspend fun <T> runCatchingAdapterTask(
task: suspend () -> CustomerAdapter.Result<T>
): CustomerAdapter.Result<T> {
return runCatching {
task()
}.fold(
onSuccess = { it },
onFailure = {
CustomerAdapter.Result.failure(cause = it, displayMessage = null)
}
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ internal class FakeCustomerAdapter(
CustomerAdapter.Result.success(null),
private val paymentMethods: CustomerAdapter.Result<List<PaymentMethod>> =
CustomerAdapter.Result.success(listOf(CARD_PAYMENT_METHOD)),
private val onPaymentMethods: (() -> CustomerAdapter.Result<List<PaymentMethod>>)? = {
paymentMethods
},
private val onGetPaymentOption: (() -> CustomerAdapter.Result<CustomerAdapter.PaymentOption?>)? = {
selectedPaymentOption
},
private val onSetSelectedPaymentOption:
((paymentOption: CustomerAdapter.PaymentOption?) -> CustomerAdapter.Result<Unit>)? = null,
private val onAttachPaymentMethod: ((paymentMethodId: String) -> CustomerAdapter.Result<PaymentMethod>)? = null,
Expand All @@ -22,7 +28,7 @@ internal class FakeCustomerAdapter(
) : CustomerAdapter {

override suspend fun retrievePaymentMethods(): CustomerAdapter.Result<List<PaymentMethod>> {
return paymentMethods
return onPaymentMethods?.invoke() ?: paymentMethods
}

override suspend fun attachPaymentMethod(paymentMethodId: String): CustomerAdapter.Result<PaymentMethod> {
Expand Down Expand Up @@ -57,7 +63,7 @@ internal class FakeCustomerAdapter(
}

override suspend fun retrieveSelectedPaymentOption(): CustomerAdapter.Result<CustomerAdapter.PaymentOption?> {
return selectedPaymentOption
return onGetPaymentOption?.invoke() ?: selectedPaymentOption
}

override suspend fun setupIntentClientSecretForCustomerAttach(): CustomerAdapter.Result<String> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,27 @@ class CustomerAdapterDataSourceTest {
assertThat(failedResult.displayMessage).isEqualTo("Something went wrong!")
}

@Test
fun `on retrieve payment methods, should catch and fail if an exception is thrown from adapter`() = runTest {
val exception = IllegalStateException("Failed to get payment methods!")

val dataSource = createCustomerAdapterDataSource(
adapter = FakeCustomerAdapter(
onPaymentMethods = {
throw exception
}
)
)

val result = dataSource.retrievePaymentMethods()

assertThat(result).isInstanceOf<CustomerSheetDataResult.Failure<List<PaymentMethod>>>()

val failedResult = result.asFailure()

assertThat(failedResult.cause).isEqualTo(exception)
}

@Test
fun `on retrieve payment option, should complete successfully from adapter`() = runTest {
val paymentOptionId = "pm_1"
Expand Down Expand Up @@ -105,6 +126,27 @@ class CustomerAdapterDataSourceTest {
assertThat(failedResult.displayMessage).isEqualTo("Something went wrong!")
}

@Test
fun `on retrieve payment option, should catch and fail if an exception is thrown from adapter`() = runTest {
val exception = IllegalStateException("Failed to retrieve saved selection!")

val dataSource = createCustomerAdapterDataSource(
adapter = FakeCustomerAdapter(
onGetPaymentOption = {
throw exception
}
)
)

val result = dataSource.retrieveSavedSelection()

assertThat(result).isInstanceOf<CustomerSheetDataResult.Failure<SavedSelection?>>()

val failedResult = result.asFailure()

assertThat(failedResult.cause).isEqualTo(exception)
}

@Test
fun `on set saved selection, should complete successfully from adapter`() = runTest {
val dataSource = createCustomerAdapterDataSource(
Expand All @@ -117,7 +159,7 @@ class CustomerAdapterDataSourceTest {

val result = dataSource.setSavedSelection(SavedSelection.GooglePay)

assertThat(result).isInstanceOf<CustomerSheetDataResult.Success<SavedSelection?>>()
assertThat(result).isInstanceOf<CustomerSheetDataResult.Success<Unit>>()
}

@Test
Expand All @@ -135,7 +177,7 @@ class CustomerAdapterDataSourceTest {

val result = dataSource.setSavedSelection(SavedSelection.GooglePay)

assertThat(result).isInstanceOf<CustomerSheetDataResult.Failure<SavedSelection?>>()
assertThat(result).isInstanceOf<CustomerSheetDataResult.Failure<Unit>>()

val failedResult = result.asFailure()

Expand All @@ -144,6 +186,27 @@ class CustomerAdapterDataSourceTest {
assertThat(failedResult.displayMessage).isEqualTo("Something went wrong!")
}

@Test
fun `on set saved selection, should catch and fail if an exception is thrown from adapter`() = runTest {
val exception = IllegalStateException("Failed to set selection!")

val dataSource = createCustomerAdapterDataSource(
adapter = FakeCustomerAdapter(
onSetSelectedPaymentOption = {
throw exception
}
)
)

val result = dataSource.setSavedSelection(SavedSelection.GooglePay)

assertThat(result).isInstanceOf<CustomerSheetDataResult.Failure<Unit>>()

val failedResult = result.asFailure()

assertThat(failedResult.cause).isEqualTo(exception)
}

@Test
fun `on attach payment method, should complete successfully from adapter`() = runTest {
val paymentMethod = PaymentMethodFactory.card(id = "pm_1")
Expand Down Expand Up @@ -188,6 +251,27 @@ class CustomerAdapterDataSourceTest {
assertThat(failedResult.displayMessage).isEqualTo("Something went wrong!")
}

@Test
fun `on attach payment method, should catch and fail if an exception is thrown from adapter`() = runTest {
val exception = IllegalStateException("Failed to attach!")

val dataSource = createCustomerAdapterDataSource(
adapter = FakeCustomerAdapter(
onAttachPaymentMethod = {
throw exception
}
)
)

val result = dataSource.attachPaymentMethod(paymentMethodId = "pm_1")

assertThat(result).isInstanceOf<CustomerSheetDataResult.Failure<PaymentMethod>>()

val failedResult = result.asFailure()

assertThat(failedResult.cause).isEqualTo(exception)
}

@Test
fun `on detach payment method, should complete successfully from adapter`() = runTest {
val paymentMethod = PaymentMethodFactory.card(id = "pm_1")
Expand Down Expand Up @@ -232,6 +316,27 @@ class CustomerAdapterDataSourceTest {
assertThat(failedResult.displayMessage).isEqualTo("Something went wrong!")
}

@Test
fun `on detach payment method, should catch and fail if an exception is thrown from adapter`() = runTest {
val exception = IllegalStateException("Failed to detach!")

val dataSource = createCustomerAdapterDataSource(
adapter = FakeCustomerAdapter(
onDetachPaymentMethod = {
throw exception
}
)
)

val result = dataSource.detachPaymentMethod(paymentMethodId = "pm_1")

assertThat(result).isInstanceOf<CustomerSheetDataResult.Failure<PaymentMethod>>()

val failedResult = result.asFailure()

assertThat(failedResult.cause).isEqualTo(exception)
}

@Test
fun `on update payment method, should complete successfully from adapter`() = runTest {
val paymentMethod = PaymentMethodFactory.card(id = "pm_1")
Expand Down Expand Up @@ -282,6 +387,30 @@ class CustomerAdapterDataSourceTest {
assertThat(failedResult.displayMessage).isEqualTo("Something went wrong!")
}

@Test
fun `on update payment method, should catch and fail if an exception is thrown from adapter`() = runTest {
val exception = IllegalStateException("Failed to update!")

val dataSource = createCustomerAdapterDataSource(
adapter = FakeCustomerAdapter(
onUpdatePaymentMethod = { _, _ ->
throw exception
}
)
)

val result = dataSource.updatePaymentMethod(
paymentMethodId = "pm_1",
params = PaymentMethodUpdateParams.createCard(expiryYear = 2028, expiryMonth = 7),
)

assertThat(result).isInstanceOf<CustomerSheetDataResult.Failure<PaymentMethod>>()

val failedResult = result.asFailure()

assertThat(failedResult.cause).isEqualTo(exception)
}

@Test
fun `on can create setup intents, should return true from adapter`() = runTest {
val dataSource = createCustomerAdapterDataSource(
Expand Down Expand Up @@ -347,6 +476,27 @@ class CustomerAdapterDataSourceTest {
assertThat(failedResult.displayMessage).isEqualTo("Something went wrong!")
}

@Test
fun `on fetch setup intent client secret, should catch & fail if exception is thrown from adapter`() = runTest {
val exception = IllegalStateException("Failed to update!")

val dataSource = createCustomerAdapterDataSource(
adapter = FakeCustomerAdapter(
onSetupIntentClientSecretForCustomerAttach = {
throw exception
}
)
)

val result = dataSource.retrieveSetupIntentClientSecret()

assertThat(result).isInstanceOf<CustomerSheetDataResult.Failure<String>>()

val failedResult = result.asFailure()

assertThat(failedResult.cause).isEqualTo(exception)
}

@Test
fun `on load customer sheet session, should load properly & report success events`() = runTest {
val intent = SetupIntentFactory.create()
Expand Down

0 comments on commit 89099c9

Please sign in to comment.