-
Notifications
You must be signed in to change notification settings - Fork 629
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add
detachPaymentMethodAndDuplicates
extension function. (#8467)
* Add `detachPaymentMethodAndDuplicates` extension function. * Update with PR comments.
- Loading branch information
1 parent
20254c3
commit 6d5c460
Showing
3 changed files
with
346 additions
and
2 deletions.
There are no files selected for viewing
81 changes: 81 additions & 0 deletions
81
...sheet/src/main/java/com/stripe/android/paymentsheet/repositories/CustomerRepositoryKtx.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
package com.stripe.android.paymentsheet.repositories | ||
|
||
import com.stripe.android.model.PaymentMethod | ||
|
||
/** | ||
* Removes the provided saved payment method alongside any duplicate stored payment methods. This function should be | ||
* deleted once an endpoint is stood up to handle detaching and removing duplicates. | ||
* | ||
* @param customerInfo authentication information that can perform detaching operations. | ||
* @param paymentMethodId the id of the payment method to remove and to compare with for stored duplicates | ||
* | ||
* @return a list of removal results that identify the payment methods that were successfully removed and the payment | ||
* methods that failed to be removed. | ||
*/ | ||
internal suspend fun CustomerRepository.detachCardPaymentMethodAndDuplicates( | ||
customerInfo: CustomerRepository.CustomerInfo, | ||
paymentMethodId: String, | ||
): List<PaymentMethodRemovalResult> { | ||
val paymentMethods = getPaymentMethods( | ||
customerInfo = customerInfo, | ||
// We only support removing duplicate cards. | ||
types = listOf(PaymentMethod.Type.Card), | ||
silentlyFail = false, | ||
).getOrElse { | ||
return listOf( | ||
PaymentMethodRemovalResult( | ||
paymentMethodId = paymentMethodId, | ||
result = Result.failure(it) | ||
) | ||
) | ||
} | ||
|
||
val requestedPaymentMethodToRemove = paymentMethods.find { paymentMethod -> | ||
paymentMethod.id == paymentMethodId | ||
} ?: throw IllegalArgumentException("Payment method with id '$paymentMethodId' does not exist!") | ||
|
||
val paymentMethodRemovalResults = mutableListOf<PaymentMethodRemovalResult>() | ||
|
||
val paymentMethodsToRemove = paymentMethods.filter { paymentMethod -> | ||
paymentMethod.type == PaymentMethod.Type.Card && | ||
paymentMethod.card?.fingerprint == requestedPaymentMethodToRemove.card?.fingerprint | ||
} | ||
|
||
paymentMethodsToRemove.forEachIndexed { index, paymentMethod -> | ||
val paymentMethodIdToRemove = paymentMethod.id | ||
|
||
val result = paymentMethodIdToRemove?.let { id -> | ||
detachPaymentMethod( | ||
customerInfo = customerInfo, | ||
paymentMethodId = id | ||
) | ||
} ?: Result.failure( | ||
NoPaymentMethodIdOnRemovalException( | ||
index = index, | ||
paymentMethod = paymentMethod | ||
) | ||
) | ||
|
||
paymentMethodRemovalResults.add( | ||
PaymentMethodRemovalResult( | ||
paymentMethodId = paymentMethodIdToRemove, | ||
result = result, | ||
) | ||
) | ||
} | ||
|
||
return paymentMethodRemovalResults | ||
} | ||
|
||
internal class NoPaymentMethodIdOnRemovalException( | ||
val index: Int, | ||
val paymentMethod: PaymentMethod, | ||
) : Exception() { | ||
override val message: String = | ||
"A payment method at index '$index' with type '${paymentMethod.type}' does not have an ID!" | ||
} | ||
|
||
internal data class PaymentMethodRemovalResult( | ||
val paymentMethodId: String?, | ||
val result: Result<PaymentMethod>, | ||
) |
246 changes: 246 additions & 0 deletions
246
...t/src/test/java/com/stripe/android/paymentsheet/repositories/CustomerRepositoryKtxTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,246 @@ | ||
package com.stripe.android.paymentsheet.repositories | ||
|
||
import com.google.common.truth.Truth.assertThat | ||
import com.stripe.android.testing.PaymentMethodFactory | ||
import com.stripe.android.utils.FakeCustomerRepository | ||
import kotlinx.coroutines.test.runTest | ||
import org.junit.Test | ||
import kotlin.test.assertFails | ||
|
||
class CustomerRepositoryKtxTest { | ||
@Test | ||
fun `'detachPaymentMethodAndDuplicates' attempts to remove all duplicate cards with matching fingerprints`() = | ||
runTest { | ||
val fingerprint = "4343434343" | ||
val differentFingerprint = "5454545454" | ||
|
||
val cards = PaymentMethodFactory.cards(size = 5) | ||
val cardsWithFingerprints = cards.mapIndexed { index, paymentMethod -> | ||
if (index < 3) { | ||
paymentMethod.copy( | ||
card = paymentMethod.card?.copy( | ||
fingerprint = fingerprint | ||
) | ||
) | ||
} else { | ||
paymentMethod.copy( | ||
card = paymentMethod.card?.copy( | ||
fingerprint = differentFingerprint | ||
) | ||
) | ||
} | ||
} | ||
|
||
val repository = FakeCustomerRepository( | ||
paymentMethods = cardsWithFingerprints, | ||
onDetachPaymentMethod = { | ||
Result.success(cards.first()) | ||
} | ||
) | ||
|
||
repository.detachCardPaymentMethodAndDuplicates( | ||
customerInfo = CustomerRepository.CustomerInfo( | ||
id = "cus_1", | ||
ephemeralKeySecret = "ephemeral_key_secret", | ||
), | ||
paymentMethodId = cards[0].id!! | ||
) | ||
|
||
assertThat(repository.detachRequests[0].paymentMethodId) | ||
.isEqualTo(cards[0].id!!) | ||
|
||
assertThat(repository.detachRequests[1].paymentMethodId) | ||
.isEqualTo(cards[1].id!!) | ||
|
||
assertThat(repository.detachRequests[2].paymentMethodId) | ||
.isEqualTo(cards[2].id!!) | ||
} | ||
|
||
@Test | ||
fun `'detachPaymentMethodAndDuplicates' returns both successful and failed removals`() = | ||
runTest { | ||
val cards = PaymentMethodFactory.cards(size = 5).map { paymentMethod -> | ||
paymentMethod.copy( | ||
card = paymentMethod.card?.copy( | ||
fingerprint = "4343434343" | ||
) | ||
) | ||
} | ||
|
||
val repository = FakeCustomerRepository( | ||
paymentMethods = cards, | ||
onDetachPaymentMethod = { paymentMethodId -> | ||
if (paymentMethodId == cards[3].id || paymentMethodId == cards[4].id) { | ||
Result.failure(TestException("Failed!")) | ||
} else { | ||
val card = cards.find { paymentMethod -> | ||
paymentMethodId == paymentMethod.id | ||
}!! | ||
|
||
Result.success(card) | ||
} | ||
} | ||
) | ||
|
||
val results = repository.detachCardPaymentMethodAndDuplicates( | ||
customerInfo = CustomerRepository.CustomerInfo( | ||
id = "cus_1", | ||
ephemeralKeySecret = "ephemeral_key_secret", | ||
), | ||
paymentMethodId = cards.first().id!! | ||
) | ||
|
||
assertThat(results).containsExactly( | ||
PaymentMethodRemovalResult( | ||
paymentMethodId = cards[0].id!!, | ||
result = Result.success(cards[0]) | ||
), | ||
PaymentMethodRemovalResult( | ||
paymentMethodId = cards[1].id!!, | ||
result = Result.success(cards[1]) | ||
), | ||
PaymentMethodRemovalResult( | ||
paymentMethodId = cards[2].id!!, | ||
result = Result.success(cards[2]) | ||
), | ||
PaymentMethodRemovalResult( | ||
paymentMethodId = cards[3].id!!, | ||
result = Result.failure(TestException("Failed!")) | ||
), | ||
PaymentMethodRemovalResult( | ||
paymentMethodId = cards[4].id!!, | ||
result = Result.failure(TestException("Failed!")) | ||
) | ||
) | ||
} | ||
|
||
@Test | ||
fun `'detachCardPaymentMethodAndDuplicates' returns payment method object received from removal operation`() = | ||
runTest { | ||
val cards = PaymentMethodFactory.cards(size = 2).map { paymentMethod -> | ||
paymentMethod.copy( | ||
customerId = "cus_111", | ||
card = paymentMethod.card?.copy(fingerprint = "4343434343"), | ||
) | ||
} | ||
|
||
val repository = FakeCustomerRepository( | ||
paymentMethods = cards, | ||
onDetachPaymentMethod = { paymentMethodId -> | ||
Result.success( | ||
cards.find { paymentMethod -> | ||
paymentMethod.id == paymentMethodId | ||
}!!.copy( | ||
customerId = null | ||
) | ||
) | ||
} | ||
) | ||
|
||
val results = repository.detachCardPaymentMethodAndDuplicates( | ||
customerInfo = CustomerRepository.CustomerInfo( | ||
id = "cus_1", | ||
ephemeralKeySecret = "ephemeral_key_secret", | ||
), | ||
paymentMethodId = cards.first().id!! | ||
) | ||
|
||
assertThat(results).containsExactly( | ||
PaymentMethodRemovalResult( | ||
paymentMethodId = cards.first().id!!, | ||
result = Result.success(cards.first().copy(customerId = null)) | ||
), | ||
PaymentMethodRemovalResult( | ||
paymentMethodId = cards.last().id!!, | ||
result = Result.success(cards.last().copy(customerId = null)) | ||
) | ||
) | ||
} | ||
|
||
@Test | ||
fun `'detachPaymentMethodAndDuplicates' returns single removal failure if fails to fetch payment methods`() = | ||
runTest { | ||
val cards = PaymentMethodFactory.cards(size = 2) | ||
val repository = FakeCustomerRepository( | ||
paymentMethods = cards, | ||
onGetPaymentMethods = { | ||
Result.failure(TestException("Failed!")) | ||
}, | ||
) | ||
|
||
val results = repository.detachCardPaymentMethodAndDuplicates( | ||
customerInfo = CustomerRepository.CustomerInfo( | ||
id = "cus_1", | ||
ephemeralKeySecret = "ephemeral_key_secret", | ||
), | ||
paymentMethodId = cards.first().id!! | ||
) | ||
|
||
assertThat(results).containsExactly( | ||
PaymentMethodRemovalResult( | ||
paymentMethodId = cards.first().id!!, | ||
result = Result.failure(TestException("Failed!")) | ||
), | ||
) | ||
} | ||
|
||
@Test | ||
fun `'detachPaymentMethodAndDuplicates' returns failure if payment method does not exist`() = | ||
runTest { | ||
val repository = FakeCustomerRepository( | ||
paymentMethods = listOf(), | ||
) | ||
|
||
val exception = assertFails { | ||
repository.detachCardPaymentMethodAndDuplicates( | ||
customerInfo = CustomerRepository.CustomerInfo( | ||
id = "cus_1", | ||
ephemeralKeySecret = "ephemeral_key_secret", | ||
), | ||
paymentMethodId = "pm_1" | ||
) | ||
} | ||
|
||
assertThat(exception).isInstanceOf(IllegalArgumentException::class.java) | ||
assertThat(exception.message).isEqualTo("Payment method with id 'pm_1' does not exist!") | ||
} | ||
|
||
@Test | ||
fun `'detachPaymentMethodAndDuplicates' returns exception in result if a payment method has no id`() = | ||
runTest { | ||
val cards = PaymentMethodFactory.cards(size = 2).mapIndexed { index, paymentMethod -> | ||
if (index == 1) { | ||
paymentMethod.copy(id = null) | ||
} else { | ||
paymentMethod | ||
} | ||
} | ||
|
||
val repository = FakeCustomerRepository( | ||
paymentMethods = cards, | ||
onDetachPaymentMethod = { | ||
Result.success(cards.first()) | ||
} | ||
) | ||
|
||
val results = repository.detachCardPaymentMethodAndDuplicates( | ||
customerInfo = CustomerRepository.CustomerInfo( | ||
id = "cus_1", | ||
ephemeralKeySecret = "ephemeral_key_secret", | ||
), | ||
paymentMethodId = cards.first().id!! | ||
) | ||
|
||
assertThat(results[0]).isEqualTo( | ||
PaymentMethodRemovalResult( | ||
paymentMethodId = cards[0].id!!, | ||
result = Result.success(cards[0]) | ||
) | ||
) | ||
|
||
assertThat(results[1].result.exceptionOrNull()) | ||
.isInstanceOf(NoPaymentMethodIdOnRemovalException::class.java) | ||
} | ||
|
||
private data class TestException(override val message: String?) : Exception() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters