Braintree SDK Version
5.21.0
Environment
Sandbox
Android Version & Device
Android 16 & Foldable Device
Braintree dependencies
com.braintreepayments.api:three-d-secure
Describe the bug
When using the Braintree Android SDK for 3D Secure authentication on foldable devices (e.g., Samsung Galaxy Z Fold), the SDK makes a duplicate API call with an already-consumed nonce after the activity is recreated due to configuration changes (fold/unfold). This results in a 422 error with message "Nonce is already consumed".
Device: Samsung Galaxy Z Fold series and other foldable devices
Android Version: 16
Scenario: Native 3DS flow using ThreeDSecureClient
Stack Trace
com.braintreepayments.api.threedsecure.ThreeDSecureClient.initializeCardinalClient$lambda$2$lambda$1(ThreeDSecureClient.kt:132)com.braintreepayments.api.threedsecure.ThreeDSecureClient.callbackCreatePaymentAuthFailure(ThreeDSecureClient.kt:407)
Root Cause Analysis
When the activity is recreated:
The ThreeDSecureLauncher is re-registered (via ActivityResultLauncher)
Our app calls launch() again to restore the payment state
The SDK's ThreeDSecureClient.createPaymentAuthRequest() makes a new API call
The nonce from step 3 has already been consumed in the original request
Server returns 422 error
The SDK's ActivityResultLauncher correctly preserves the 3DS flow continuation, but there's no mechanism to prevent re-launching with a consumed nonce.
Overriding configuration changes on the host activity didn't help, as there is underlying view shown in the SDK, wonder if this is a real bug or can be avoided in some way.
To reproduce
- Start a 3D Secure payment flow using ThreeDSecureLauncher.launch()
- When the 3DS challenge screen appears, fold or unfold the device
- This triggers Android's configuration change, causing activity recreation
- Complete the 3DS authentication
Observe the error in logs
Expected Behavior
The SDK should handle activity recreation gracefully through its ActivityResultLauncher mechanism without making duplicate API calls. The payment should complete successfully after fold/unfold.
Actual Behavior
After activity recreation, the SDK makes a new API call to the 3DS lookup endpoint with the same nonce that was already consumed, resulting in:
Unexpected code Response{protocol=h2, code=422, message=, url=https://api.sandbox.braintreegateway.com/merchants/.../client_api/v1/payment_methods/.../three_d_secure/lookup} with body {"error":{"message":"Nonce is already consumed"},"threeDSecureInfo":{"liabilityShifted":false,"liabilityShiftPossible":false}}
Expected behavior
Activity Recreation During Active 3DS Flow
User initiates payment → launch() called with fresh nonce
3DS challenge screen displayed to user
Device folded/unfolded → Activity destroyed and recreated by Android
Activity onCreate() called again → register() called to re-establish callback
3DS challenge continues in the same session (handled by SDK's ActivityResultLauncher)
User completes authentication
SDK delivers result to the registered callback without making a new API call
Payment completes successfully using the tokenized result
Screenshots
No response
Braintree SDK Version
5.21.0
Environment
Sandbox
Android Version & Device
Android 16 & Foldable Device
Braintree dependencies
com.braintreepayments.api:three-d-secure
Describe the bug
When using the Braintree Android SDK for 3D Secure authentication on foldable devices (e.g., Samsung Galaxy Z Fold), the SDK makes a duplicate API call with an already-consumed nonce after the activity is recreated due to configuration changes (fold/unfold). This results in a 422 error with message "Nonce is already consumed".
Device: Samsung Galaxy Z Fold series and other foldable devices
Android Version: 16
Scenario: Native 3DS flow using ThreeDSecureClient
Stack Trace
com.braintreepayments.api.threedsecure.ThreeDSecureClient.initializeCardinalClient$lambda$2$lambda$1(ThreeDSecureClient.kt:132)com.braintreepayments.api.threedsecure.ThreeDSecureClient.callbackCreatePaymentAuthFailure(ThreeDSecureClient.kt:407)
Root Cause Analysis
When the activity is recreated:
The ThreeDSecureLauncher is re-registered (via ActivityResultLauncher)
Our app calls launch() again to restore the payment state
The SDK's ThreeDSecureClient.createPaymentAuthRequest() makes a new API call
The nonce from step 3 has already been consumed in the original request
Server returns 422 error
The SDK's ActivityResultLauncher correctly preserves the 3DS flow continuation, but there's no mechanism to prevent re-launching with a consumed nonce.
Overriding configuration changes on the host activity didn't help, as there is underlying view shown in the SDK, wonder if this is a real bug or can be avoided in some way.
To reproduce
Observe the error in logs
Expected Behavior
The SDK should handle activity recreation gracefully through its ActivityResultLauncher mechanism without making duplicate API calls. The payment should complete successfully after fold/unfold.
Actual Behavior
After activity recreation, the SDK makes a new API call to the 3DS lookup endpoint with the same nonce that was already consumed, resulting in:
Unexpected code Response{protocol=h2, code=422, message=, url=https://api.sandbox.braintreegateway.com/merchants/.../client_api/v1/payment_methods/.../three_d_secure/lookup} with body {"error":{"message":"Nonce is already consumed"},"threeDSecureInfo":{"liabilityShifted":false,"liabilityShiftPossible":false}}
Expected behavior
Activity Recreation During Active 3DS Flow
User initiates payment → launch() called with fresh nonce
3DS challenge screen displayed to user
Device folded/unfolded → Activity destroyed and recreated by Android
Activity onCreate() called again → register() called to re-establish callback
3DS challenge continues in the same session (handled by SDK's ActivityResultLauncher)
User completes authentication
SDK delivers result to the registered callback without making a new API call
Payment completes successfully using the tokenized result
Screenshots
No response