From aeb503c35cef53d4c38d5afb9b1f45bd502a5347 Mon Sep 17 00:00:00 2001 From: Jay Newstrom Date: Wed, 18 Dec 2024 14:02:59 -0600 Subject: [PATCH] Call new elements/detach endpoint for customer sessions. (#9801) --- .../testing/AbsFakeStripeRepository.kt | 9 +++++ .../android/networking/StripeApiRepository.kt | 38 +++++++++++++++++++ .../android/networking/StripeRepository.kt | 8 ++++ .../networking/StripeApiRepositoryTest.kt | 12 ++++++ .../repositories/CustomerApiRepository.kt | 26 +++++++++---- ...ustomerSessionCustomerSheetActivityTest.kt | 2 +- .../repositories/CustomerRepositoryTest.kt | 37 ++++++++++++++++++ 7 files changed, 124 insertions(+), 8 deletions(-) diff --git a/payments-core-testing/src/main/java/com/stripe/android/testing/AbsFakeStripeRepository.kt b/payments-core-testing/src/main/java/com/stripe/android/testing/AbsFakeStripeRepository.kt index 6894ff9d687..b47078e6ef2 100644 --- a/payments-core-testing/src/main/java/com/stripe/android/testing/AbsFakeStripeRepository.kt +++ b/payments-core-testing/src/main/java/com/stripe/android/testing/AbsFakeStripeRepository.kt @@ -184,6 +184,15 @@ abstract class AbsFakeStripeRepository : StripeRepository { TODO("Not yet implemented") } + override suspend fun detachPaymentMethod( + customerSessionClientSecret: String, + productUsageTokens: Set, + paymentMethodId: String, + requestOptions: ApiRequest.Options + ): Result { + TODO("Not yet implemented") + } + override suspend fun getPaymentMethods( listPaymentMethodsParams: ListPaymentMethodsParams, productUsageTokens: Set, diff --git a/payments-core/src/main/java/com/stripe/android/networking/StripeApiRepository.kt b/payments-core/src/main/java/com/stripe/android/networking/StripeApiRepository.kt index 64c808f389e..817c2ea277b 100644 --- a/payments-core/src/main/java/com/stripe/android/networking/StripeApiRepository.kt +++ b/payments-core/src/main/java/com/stripe/android/networking/StripeApiRepository.kt @@ -747,6 +747,36 @@ class StripeApiRepository @JvmOverloads internal constructor( } } + /** + * Analytics event: [PaymentAnalyticsEvent.CustomerDetachPaymentMethod] + */ + @Throws( + InvalidRequestException::class, + APIConnectionException::class, + APIException::class, + AuthenticationException::class, + CardException::class + ) + override suspend fun detachPaymentMethod( + customerSessionClientSecret: String, + productUsageTokens: Set, + paymentMethodId: String, + requestOptions: ApiRequest.Options + ): Result { + return fetchStripeModelResult( + apiRequest = apiRequestFactory.createPost( + url = getElementsDetachPaymentMethodUrl(paymentMethodId), + options = requestOptions, + params = mapOf("customer_session_client_secret" to customerSessionClientSecret), + ), + jsonParser = PaymentMethodJsonParser() + ) { + fireAnalyticsRequest( + paymentAnalyticsRequestFactory.createDetachPaymentMethod(productUsageTokens) + ) + } + } + /** * Retrieve a Customer's [PaymentMethod]s * @@ -1383,6 +1413,14 @@ class StripeApiRepository @JvmOverloads internal constructor( return getApiUrl("payment_methods/%s/detach", paymentMethodId) } + /** + * @return `https://api.stripe.com/v1/payment_methods/:id/detach` + */ + @VisibleForTesting + internal fun getElementsDetachPaymentMethodUrl(paymentMethodId: String): String { + return getApiUrl("elements/payment_methods/%s/detach", paymentMethodId) + } + override suspend fun retrieveElementsSession( params: ElementsSessionParams, options: ApiRequest.Options, diff --git a/payments-core/src/main/java/com/stripe/android/networking/StripeRepository.kt b/payments-core/src/main/java/com/stripe/android/networking/StripeRepository.kt index c2e526fab51..c82c44269d6 100644 --- a/payments-core/src/main/java/com/stripe/android/networking/StripeRepository.kt +++ b/payments-core/src/main/java/com/stripe/android/networking/StripeRepository.kt @@ -170,6 +170,14 @@ interface StripeRepository { requestOptions: ApiRequest.Options ): Result + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) + suspend fun detachPaymentMethod( + customerSessionClientSecret: String, + productUsageTokens: Set, + paymentMethodId: String, + requestOptions: ApiRequest.Options + ): Result + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) suspend fun getPaymentMethods( listPaymentMethodsParams: ListPaymentMethodsParams, diff --git a/payments-core/src/test/java/com/stripe/android/networking/StripeApiRepositoryTest.kt b/payments-core/src/test/java/com/stripe/android/networking/StripeApiRepositoryTest.kt index 15df02decf3..36b43882141 100644 --- a/payments-core/src/test/java/com/stripe/android/networking/StripeApiRepositoryTest.kt +++ b/payments-core/src/test/java/com/stripe/android/networking/StripeApiRepositoryTest.kt @@ -197,6 +197,18 @@ internal class StripeApiRepositoryTest { assertThat(detachUrl).isEqualTo(expectedUrl) } + @Test + fun testGetElementsDetachPaymentMethodUrl() { + val paymentMethodId = "pm_1ETDEa2eZvKYlo2CN5828c52" + val detachUrl = stripeApiRepository.getElementsDetachPaymentMethodUrl(paymentMethodId) + val expectedUrl = arrayOf( + "https://api.stripe.com/v1/elements/payment_methods/", + paymentMethodId, + "/detach" + ).joinToString("") + assertThat(detachUrl).isEqualTo(expectedUrl) + } + @Test fun testGetPaymentMethodsUrl() { assertThat(StripeApiRepository.paymentMethodsUrl) diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/repositories/CustomerApiRepository.kt b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/repositories/CustomerApiRepository.kt index d0ed103b03a..381a4bb535a 100644 --- a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/repositories/CustomerApiRepository.kt +++ b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/repositories/CustomerApiRepository.kt @@ -113,14 +113,26 @@ internal class CustomerApiRepository @Inject constructor( paymentMethodId ) } else { - stripeRepository.detachPaymentMethod( - productUsageTokens = productUsageTokens, - paymentMethodId = paymentMethodId, - requestOptions = ApiRequest.Options( - apiKey = customerInfo.ephemeralKeySecret, - stripeAccount = lazyPaymentConfig.get().stripeAccountId, + if (customerInfo.customerSessionClientSecret != null) { + stripeRepository.detachPaymentMethod( + customerSessionClientSecret = customerInfo.customerSessionClientSecret, + productUsageTokens = productUsageTokens, + paymentMethodId = paymentMethodId, + requestOptions = ApiRequest.Options( + apiKey = customerInfo.ephemeralKeySecret, + stripeAccount = lazyPaymentConfig.get().stripeAccountId, + ) ) - ) + } else { + stripeRepository.detachPaymentMethod( + productUsageTokens = productUsageTokens, + paymentMethodId = paymentMethodId, + requestOptions = ApiRequest.Options( + apiKey = customerInfo.ephemeralKeySecret, + stripeAccount = lazyPaymentConfig.get().stripeAccountId, + ) + ) + } } return result.onFailure { diff --git a/paymentsheet/src/test/java/com/stripe/android/customersheet/CustomerSessionCustomerSheetActivityTest.kt b/paymentsheet/src/test/java/com/stripe/android/customersheet/CustomerSessionCustomerSheetActivityTest.kt index ae56081da29..5e29a91502f 100644 --- a/paymentsheet/src/test/java/com/stripe/android/customersheet/CustomerSessionCustomerSheetActivityTest.kt +++ b/paymentsheet/src/test/java/com/stripe/android/customersheet/CustomerSessionCustomerSheetActivityTest.kt @@ -462,7 +462,7 @@ class CustomerSessionCustomerSheetActivityTest { networkRule.enqueue( host("api.stripe.com"), method("POST"), - path("/v1/payment_methods/$id/detach") + path("/v1/elements/payment_methods/$id/detach") ) { response -> response.createPaymentMethodDetachResponse(id = id) } diff --git a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/repositories/CustomerRepositoryTest.kt b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/repositories/CustomerRepositoryTest.kt index 038e9729604..c91f4555613 100644 --- a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/repositories/CustomerRepositoryTest.kt +++ b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/repositories/CustomerRepositoryTest.kt @@ -398,6 +398,28 @@ internal class CustomerRepositoryTest { assertThat(result.isFailure).isTrue() } + @Test + fun `detachPaymentMethod() should call elements endpoint when customerSessionClientSecret exists`() = + runTest { + givenElementsDetachPaymentMethodReturns( + Result.success( + PaymentMethodFixtures.CARD_PAYMENT_METHOD + ) + ) + + val result = repository.detachPaymentMethod( + customerInfo = CustomerRepository.CustomerInfo( + id = "customer_id", + ephemeralKeySecret = "ephemeral_key", + customerSessionClientSecret = "cuss_123", + ), + paymentMethodId = "payment_method_id", + canRemoveDuplicates = false, + ) + + assertThat(result.getOrNull()).isEqualTo(PaymentMethodFixtures.CARD_PAYMENT_METHOD) + } + @Test fun `detachPaymentMethod() with 'canRemoveDuplicates' as 'true' should remove duplicate payment methods and remove provided payment method ID last`() = runTest { @@ -643,6 +665,21 @@ internal class CustomerRepositoryTest { } } + private fun givenElementsDetachPaymentMethodReturns( + result: Result + ) { + stripeRepository.stub { + onBlocking { + detachPaymentMethod( + customerSessionClientSecret = any(), + productUsageTokens = any(), + paymentMethodId = anyString(), + requestOptions = any(), + ) + }.doReturn(result) + } + } + private fun givenAttachPaymentMethodReturns( result: Result ) {