Skip to content

feat: introduce ThreeDSecureProvider to PaymentAction module - #1756

Draft
noguier wants to merge 8 commits into
payment-actions-m3-featurefrom
payment-actions-3ds-wrapper
Draft

noguier wants to merge 8 commits into
payment-actions-m3-featurefrom
payment-actions-3ds-wrapper

Conversation

@noguier

@noguier noguier commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

Summary of changes

  • Add the Cardinal SDK (2.2.7-7) to :PaymentActions, plus a consumer ProGuard rule (-dontwarn com.cardinalcommerce**) matching :ThreeDSecure. As with the legacy module, apps using :PaymentActions need the Cardinal JFrog repository.
  • Add ThreeDSecureProvider, an internal provider-neutral interface the drive loop depends on. collectDeviceData() returns a sealed DeviceDataResult (Collected(dfReferenceId) or Failed(error)), so failures never throw through the loop. Switching 3DS providers later means a new implementation plus one line in PaymentActionsClient; the loop and public API stay the same.
  • Add CardinalThreeDSecureProvider, the only class that touches Cardinal. It configures Cardinal from the Braintree configuration and bridges Cardinal's init callback into a suspend call.
  • NextActionHandler now resolves 3DS device data collection (DDC). On REQUIRES_CUSTOMER_ACTION with a ThreeDSecureAuthentication next action, it collects device data, sends signalCustomerActionComplete(DeviceDataCollection(dfReferenceId)), and continues on the signal's response instead of re-fetching. A frictionless flow therefore ends at confirm or complete with no extra request. Any other next action, including a challenge, still returns CustomerActionRequired.
  • PaymentActionsClient builds the Cardinal provider in a private constructor. The public constructor is unchanged, so merchants call nothing new.
  • Stacked on payment-actions-drive-loop. The iOS equivalent is Cardinal mobile integration braintree_ios#1904. The 3DS challenge, public launch options and analytics are not part of this PR.

Cardinal configuration

Setting Value Why
environment PRODUCTION if the configuration says production, otherwise STAGING Same rule as the legacy module
requestTimeout 8000 Same as legacy
isEnableDFSync true onSetupCompleted waits until device data has reached Cardinal, so the server never gets a dfReferenceId whose data is still in flight
isEnableLogging true outside production, false in production Guidance from the 3DS team
challengeTimeout 5 Cardinal's minimum (and default), set explicitly
uiType BOTH Legacy default until launch options are added

Why it's shaped this way

  • JWT check only: a missing or empty cardinalAuthenticationJwt returns a Failure, as on iOS. There is no separate "3DS enabled" check.
  • Configuration is read when DDC runs, not at construction: merchants who never reach 3DS aren't affected by missing 3DS configuration. iOS builds its provider lazily because it owns a CardinalSession that has to span DDC and the challenge. Android's Cardinal is a process-wide singleton, so the provider holds no state.
  • First Cardinal callback wins: Cardinal calls back on its own thread and may call more than once. Resuming a coroutine twice throws, so an AtomicBoolean lets only the first callback resume it.
  • Errors reuse the loop's failure path: a DDC failure becomes a PaymentActionServiceResult.Failure, so it takes the same exit as a failed fetch or signal.

Testing

  • New CardinalThreeDSecureProviderUnitTest: configuration per environment, 2-arg init ordering, success, missing and empty JWT, setup validation failure, configure / init throwing, configuration fetch failure, and duplicate callbacks.
  • NextActionHandlerUnitTest: DDC then signal then continue (confirm, complete, PROCESSING polling, challenge hand-off), DDC failure with no signal sent, signal failure, a version decrease on the signal response, and next actions that never reach the provider.
  • PaymentActionsClientUnitTest: submitForPaymentAction goes through DDC.
  • Passing locally: :PaymentActions:testReleaseUnitTest, detekt, lintRelease, :Demo:assembleDebug.

AI Usage

Which AI Agent Was Used?

  • Copilot
  • Claude
  • Other (Type Name Here)

How was AI used?
Claude helped plan the design against the Android 3DS spike and the iOS implementation, wrote the provider, handler changes and unit tests, and drafted this description. I reviewed the design choices and the diff.

Estimated AI Code Contribution

  • less than 30%
  • 30 - 60%
  • 60 - 100%

Checklist

  • Added a changelog entry
  • Tested and confirmed payment flows affected by this change are functioning as expected

Authors

List GitHub usernames for everyone who contributed to this pull request.

🤖 Generated with Claude Code

Copilot AI balanced review requested due to automatic review settings November 9, 2026 16:33

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Repeated authentication responses can cause an unbounded loop that never returns a merchant result and leaves the in-flight guard blocking subsequent calls.

4 open issues
What changed in this PR

Adds Cardinal-backed 3D Secure device data collection to PaymentActions, allowing its existing drive loop to handle authentication setup internally while leaving other customer actions as handoff boundaries.

Changes:

  • Introduces a provider interface, result types, and Cardinal implementation.
  • Wires collection and server signaling into the handler and client.
  • Adds the Cardinal dependency, consumer ProGuard rules, and unit tests.
File Description
PaymentActions/​src/​test/​java/​com/​braintreepayments/​api/​paymentactions/​PaymentActionsClientUnitTest.kt Tests automatic collection during payment submission.
PaymentActions/​src/​test/​java/​com/​braintreepayments/​api/​paymentactions/​NextActionHandlerUnitTest.kt Tests collection, signaling, response handling, and failures.
PaymentActions/​src/​test/​java/​com/​braintreepayments/​api/​paymentactions/​CardinalThreeDSecureProviderUnitTest.kt Tests Cardinal configuration, callbacks, and setup errors.
PaymentActions/​src/​main/​java/​com/​braintreepayments/​api/​paymentactions/​ThreeDSecureProvider.kt Defines the provider boundary and collection results.
PaymentActions/​src/​main/​java/​com/​braintreepayments/​api/​paymentactions/​PaymentActionsClient.kt Constructs and injects the provider; updates behavior documentation.
PaymentActions/​src/​main/​java/​com/​braintreepayments/​api/​paymentactions/​NextActionHandler.kt Collects device data and continues from the signal response.
PaymentActions/​src/​main/​java/​com/​braintreepayments/​api/​paymentactions/​CardinalThreeDSecureProvider.kt Implements Cardinal session setup and callback adaptation.
PaymentActions/​proguard.pro Adds Cardinal warning suppression.
PaymentActions/​build.gradle Adds Cardinal and registers consumer ProGuard rules.

Review effort: Balanced


implementation libs.androidx.annotation
implementation libs.coroutines.core
implementation libs.cardinal
Comment on lines +138 to +139
* 3D Secure device data collection is performed internally when the Payment Action requires
* it. Any other customer action returns [PaymentActionResult.CustomerActionRequired].
Base automatically changed from payment-actions-drive-loop to payment-actions-m3-feature November 9, 2026 17:05
Copilot AI balanced review requested due to automatic review settings November 9, 2026 19:58

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

A unit test references an undefined result, preventing compilation, and Cardinal setup failures discard the provider’s diagnostic error details.

6 open issues

Review effort: Balanced

}
// The signal's response is the updated Payment Action, so the next turn uses it
// instead of calling getPaymentAction() again.
passedInResult = collectDeviceDataAndSignal()
Copilot AI balanced review requested due to automatic review settings November 9, 2026 23:08

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The new automatic PaymentActions 3DS device-data collection lacks an unreleased changelog entry, and DEPENDENCIES.md omits the module’s Cardinal runtime dependency.

3 open issues
3 resolved since last review

Review effort: Balanced

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants