diff --git a/CHANGELOG.md b/CHANGELOG.md index c9bc0b415cc..d5eca112446 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## X.X.X ### Payments [Fixed][5308](https://github.com/stripe/stripe-android/pull/5308) OXXO so that processing is considered a successful terminal state, similar to Konbini and Boleto. +[Fixed][5138](https://github.com/stripe/stripe-android/pull/5138) Fixed an issue where PaymentSheet will show a failure even when 3DS2 Payment/SetupIntent is successful ## 20.7.0 - 2022-07-06 * This release adds additional support for Afterpay/Clearpay in PaymentSheet. diff --git a/payments-core/src/main/java/com/stripe/android/payments/PaymentFlowResultProcessor.kt b/payments-core/src/main/java/com/stripe/android/payments/PaymentFlowResultProcessor.kt index 1d22b2dfb05..5edd03c5d2d 100644 --- a/payments-core/src/main/java/com/stripe/android/payments/PaymentFlowResultProcessor.kt +++ b/payments-core/src/main/java/com/stripe/android/payments/PaymentFlowResultProcessor.kt @@ -61,7 +61,8 @@ internal sealed class PaymentFlowResultProcessor when { - stripeIntent.status == StripeIntent.Status.Succeeded -> { + stripeIntent.status == StripeIntent.Status.Succeeded || + stripeIntent.status == StripeIntent.Status.RequiresCapture -> { createStripeIntentResult( stripeIntent, SUCCEEDED, @@ -73,11 +74,7 @@ internal sealed class PaymentFlowResultProcessor SUCCEEDED + else -> originalFlowOutcome + } + } + protected abstract suspend fun retrieveStripeIntent( clientSecret: String, requestOptions: ApiRequest.Options, @@ -181,7 +186,7 @@ internal sealed class PaymentFlowResultProcessor 1) { - val delayMs = retryDelaySupplier.getDelayMillis( - 3, - remainingRetries - ) - delay(delayMs) - stripeIntent = requireNotNull( - stripeRepository.refreshPaymentIntent( - clientSecret, - requestOptions - ) - ) - remainingRetries-- - } - - if (stripeIntent.requiresAction()) { - throw MaxRetryReachedException() - } else { - return stripeIntent - } - } override suspend fun cancelStripeIntentSource( stripeIntentId: String, diff --git a/payments-core/src/test/java/com/stripe/android/model/PaymentIntentFixtures.kt b/payments-core/src/test/java/com/stripe/android/model/PaymentIntentFixtures.kt index ecff18ad449..56140068284 100644 --- a/payments-core/src/test/java/com/stripe/android/model/PaymentIntentFixtures.kt +++ b/payments-core/src/test/java/com/stripe/android/model/PaymentIntentFixtures.kt @@ -142,7 +142,7 @@ internal object PaymentIntentFixtures { }, "source": null } - """.trimIndent() + """.trimIndent() ) } @@ -249,7 +249,7 @@ internal object PaymentIntentFixtures { }, "source": null } - """.trimIndent() + """.trimIndent() ) } diff --git a/payments-core/src/test/java/com/stripe/android/payments/PaymentIntentFlowResultProcessorTest.kt b/payments-core/src/test/java/com/stripe/android/payments/PaymentIntentFlowResultProcessorTest.kt index 17923a6a371..c6d2db6905f 100644 --- a/payments-core/src/test/java/com/stripe/android/payments/PaymentIntentFlowResultProcessorTest.kt +++ b/payments-core/src/test/java/com/stripe/android/payments/PaymentIntentFlowResultProcessorTest.kt @@ -9,6 +9,7 @@ import com.stripe.android.core.Logger import com.stripe.android.core.exception.MaxRetryReachedException import com.stripe.android.core.networking.ApiRequest import com.stripe.android.model.PaymentIntentFixtures +import com.stripe.android.model.StripeIntent import com.stripe.android.networking.StripeRepository import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.UnconfinedTestDispatcher @@ -37,7 +38,7 @@ internal class PaymentIntentFlowResultProcessorTest { { ApiKeyFixtures.FAKE_PUBLISHABLE_KEY }, mockStripeRepository, Logger.noop(), - testDispatcher, + testDispatcher ) @Test @@ -232,7 +233,7 @@ internal class PaymentIntentFlowResultProcessorTest { val result = processor.processResult( PaymentFlowResult.Unvalidated( clientSecret = clientSecret, - flowOutcome = StripeIntentResult.Outcome.CANCELED, + flowOutcome = StripeIntentResult.Outcome.CANCELED ) ) @@ -260,6 +261,54 @@ internal class PaymentIntentFlowResultProcessorTest { ) } + @Test + fun `3ds2 canceled with requires capture intent should succeed`() = + runTest { + val refreshedPaymentIntent = PaymentIntentFixtures.PI_VISA_3DS2_SUCCEEDED.copy( + status = StripeIntent.Status.RequiresCapture + ) + + whenever(mockStripeRepository.retrievePaymentIntent(any(), any(), any())).thenReturn( + PaymentIntentFixtures.PI_PROCESSING_VISA_3DS2 + ) + whenever(mockStripeRepository.refreshPaymentIntent(any(), any())).thenReturn( + refreshedPaymentIntent + ) + + val clientSecret = "pi_3L8WOsLu5o3P18Zp191FpRSy_secret_5JIwIT1ooCwRm28AwreUAc6N4" + val requestOptions = ApiRequest.Options(apiKey = ApiKeyFixtures.FAKE_PUBLISHABLE_KEY) + + val result = processor.processResult( + PaymentFlowResult.Unvalidated( + clientSecret = clientSecret, + flowOutcome = StripeIntentResult.Outcome.CANCELED + ) + ) + + verify(mockStripeRepository).retrievePaymentIntent( + eq(clientSecret), + eq(requestOptions), + eq(PaymentFlowResultProcessor.EXPAND_PAYMENT_METHOD) + ) + + verify( + mockStripeRepository, + times(1) + ).refreshPaymentIntent( + eq(clientSecret), + eq(requestOptions) + ) + + assertThat(result) + .isEqualTo( + PaymentIntentResult( + refreshedPaymentIntent, + StripeIntentResult.Outcome.SUCCEEDED, + null + ) + ) + } + @Test fun `3ds2 canceled with succeeded intent should succeed`() = runTest { @@ -273,7 +322,7 @@ internal class PaymentIntentFlowResultProcessorTest { val result = processor.processResult( PaymentFlowResult.Unvalidated( clientSecret = clientSecret, - flowOutcome = StripeIntentResult.Outcome.CANCELED, + flowOutcome = StripeIntentResult.Outcome.CANCELED ) ) @@ -318,7 +367,7 @@ internal class PaymentIntentFlowResultProcessorTest { processor.processResult( PaymentFlowResult.Unvalidated( clientSecret = clientSecret, - flowOutcome = StripeIntentResult.Outcome.CANCELED, + flowOutcome = StripeIntentResult.Outcome.CANCELED ) ) } diff --git a/payments-core/src/test/java/com/stripe/android/payments/SetupIntentFlowResultProcessorTest.kt b/payments-core/src/test/java/com/stripe/android/payments/SetupIntentFlowResultProcessorTest.kt index 9e4d41ca546..1d90a8cf715 100644 --- a/payments-core/src/test/java/com/stripe/android/payments/SetupIntentFlowResultProcessorTest.kt +++ b/payments-core/src/test/java/com/stripe/android/payments/SetupIntentFlowResultProcessorTest.kt @@ -80,7 +80,7 @@ internal class SetupIntentFlowResultProcessorTest { val result = processor.processResult( PaymentFlowResult.Unvalidated( clientSecret = clientSecret, - flowOutcome = StripeIntentResult.Outcome.CANCELED, + flowOutcome = StripeIntentResult.Outcome.CANCELED ) ) @@ -122,7 +122,7 @@ internal class SetupIntentFlowResultProcessorTest { val result = processor.processResult( PaymentFlowResult.Unvalidated( clientSecret = clientSecret, - flowOutcome = StripeIntentResult.Outcome.CANCELED, + flowOutcome = StripeIntentResult.Outcome.CANCELED ) ) @@ -165,7 +165,7 @@ internal class SetupIntentFlowResultProcessorTest { processor.processResult( PaymentFlowResult.Unvalidated( clientSecret = clientSecret, - flowOutcome = StripeIntentResult.Outcome.CANCELED, + flowOutcome = StripeIntentResult.Outcome.CANCELED ) ) }