diff --git a/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/Cancelled3DS2Exception.kt b/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/Cancelled3DS2Exception.kt index 35d3378588..5dc4f8a811 100644 --- a/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/Cancelled3DS2Exception.kt +++ b/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/Cancelled3DS2Exception.kt @@ -12,6 +12,7 @@ import com.adyen.checkout.core.exception.ComponentException /** * This exception is an indication that the 3DS2 Authentication was cancelled by the user. */ +@Deprecated("This exception is no longer in use") class Cancelled3DS2Exception(errorMessage: String) : ComponentException(errorMessage) { companion object { private const val serialVersionUID = 3858008275644429050L diff --git a/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2Delegate.kt b/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2Delegate.kt index 83f38fda4a..6286ce8ca5 100644 --- a/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2Delegate.kt +++ b/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2Delegate.kt @@ -15,7 +15,6 @@ import androidx.annotation.VisibleForTesting import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.SavedStateHandle import com.adyen.checkout.adyen3ds2.Authentication3DS2Exception -import com.adyen.checkout.adyen3ds2.Cancelled3DS2Exception import com.adyen.checkout.adyen3ds2.internal.analytics.ThreeDS2Events import com.adyen.checkout.adyen3ds2.internal.data.api.SubmitFingerprintRepository import com.adyen.checkout.adyen3ds2.internal.data.model.Adyen3DS2Serializer @@ -573,10 +572,20 @@ internal class DefaultAdyen3DS2Delegate( } } - private fun onCancelled() { + private fun onCancelled(result: ChallengeResult.Cancelled) { adyenLog(AdyenLogLevel.DEBUG) { "challenge cancelled" } - emitError(Cancelled3DS2Exception("Challenge canceled.")) - closeTransaction() + try { + val details = makeDetails(result.transactionStatus, result.additionalDetails) + emitDetails(details) + } catch (e: CheckoutException) { + trackChallengeErrorEvent( + errorEvent = ErrorEvent.THREEDS2_CHALLENGE_HANDLING, + message = "Challenge is cancelled and details cannot be created", + ) + emitError(e) + } finally { + closeTransaction() + } } private fun onTimeout(result: ChallengeResult.Timeout) { @@ -615,7 +624,7 @@ internal class DefaultAdyen3DS2Delegate( when (result) { is ChallengeResult.Cancelled -> { trackChallengeCompletedEvent(ThreeDS2Events.Result.CANCELLED) - onCancelled() + onCancelled(result) } is ChallengeResult.Completed -> { diff --git a/3ds2/src/test/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2DelegateTest.kt b/3ds2/src/test/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2DelegateTest.kt index cec5f7ea90..1a099a3768 100644 --- a/3ds2/src/test/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2DelegateTest.kt +++ b/3ds2/src/test/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2DelegateTest.kt @@ -16,7 +16,6 @@ import android.content.Context import android.content.Intent import androidx.lifecycle.SavedStateHandle import com.adyen.checkout.adyen3ds2.Authentication3DS2Exception -import com.adyen.checkout.adyen3ds2.Cancelled3DS2Exception import com.adyen.checkout.adyen3ds2.internal.analytics.ThreeDS2Events import com.adyen.checkout.adyen3ds2.internal.data.api.SubmitFingerprintRepository import com.adyen.checkout.adyen3ds2.internal.data.model.Adyen3DS2Serializer @@ -489,8 +488,8 @@ internal class DefaultAdyen3DS2DelegateTest( } @Test - fun `cancelled, then an error is emitted`() = runTest { - val exceptionFlow = delegate.exceptionFlow.test(testScheduler) + fun `cancelled, then details are emitted`() = runTest { + val detailsFlow = delegate.detailsFlow.test(testScheduler) delegate.onCompletion( result = ChallengeResult.Cancelled( @@ -499,11 +498,11 @@ internal class DefaultAdyen3DS2DelegateTest( ), ) - assertTrue(exceptionFlow.latestValue is Cancelled3DS2Exception) + assertNotNull(detailsFlow.latestValue.details) } @Test - fun `timedout, then details are emitted`() = runTest { + fun `timed out, then details are emitted`() = runTest { val detailsFlow = delegate.detailsFlow.test(testScheduler) delegate.onCompletion( diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index ab6d91f712..730bcc27ab 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -17,12 +17,16 @@ ## Improved ## Changed +- For 3DS2 native flow, cancellations by shopper trigger `onAdditionalDetails()` event. You can make a `/payments/details` call to gain more insight into the transaction. + - If you already handle other 3DS2 errors by making a `/payments/details` call, then no changes are required and the specific handling for `Cancelled3DS2Exception` can be removed. + - If not yet implemented, you should update your systems to handle shopper cancellations using the new flow. - Dependency versions: | Name | Version | |--------------------------------------------------------------------------------------------------------|-------------------------------| | | | ## Deprecated +- `Cancelled3DS2Exception` is now deprecated. Shopper cancellation of the 3DS2 native flow triggers the `onAdditionalDetails()` event, enabling a `/payments/details` call for transaction insights. - The styles and strings for the Cash App Pay loading indicator. Use the new styles and strings instead. | Previous | Now | |-----------------------------------------------------------|------------------------------------------------------------------|