Repository navigation
fix(ad-hoc): reuse Checkout 3DS transaction for challenge - #547
Merged
Merged
Conversation
Checkout's createTransaction() closes the current transaction and creates a new one with fresh SDK transaction ID and ephemeral keys, so performing the challenge on a second transaction did not match the authentication request. * Retain transaction created during authentication and reuse it for challenge. * Track service state explicitly to match PONetcetera3DS2Service. * Release SDK resources when authentication fails. * Close transaction on clean().
* Add internal initializer to inject Checkout 3DS service provider. * Port Netcetera 3DS service tests to Checkout 3DS service. * Verify that challenge reuses authentication transaction. * Rely on Checkout cleanUp() to close current transaction. * Disable type_body_length for PONetcetera3DS2Service.
* Remove internal initializer and service provider seam. * Remove mocked transaction reuse tests. * Decode request fixtures instead of using internal initializers.
* Close transaction before cleaning up service instead of relying on cleanUp(). * Remove unreachable cleaning state since teardown is synchronous.
There was a problem hiding this comment.
🟡 Changes recommended
The tests leave in-flight authentication rejection untested and allow unrelated validation errors to satisfy the transaction-ID mismatch case.
2 open issues
What changed in this PR
Fixes the Checkout 3DS regression introduced by the async rework by reusing the authentication transaction for the challenge, preserving its SDK transaction ID and keys.
Changes:
- Add explicit lifecycle states, reject overlapping operations and mismatched transaction IDs, and tear down resources after failures or cleanup.
- Replace the placeholder test with public-API integration tests and restore Checkout 3DS testing in CI.
- Split Netcetera’s logger declaration and add a type-length lint suppression without changing runtime behavior.
| File | Description |
|---|---|
Tests/ProcessOutCheckout3DSTests/Sources/Integration/Service/Checkout3DSServiceTests.swift |
Add authentication, state, cleanup, and challenge-rejection tests. |
Tests/ProcessOutCheckout3DSTests/Sources/Checkout3DSServiceTests.swift |
Remove the placeholder test suite. |
Sources/ProcessOutNetcetera3DS/Sources/Service/PONetcetera3DS2Service.swift |
Split property declarations and suppress the type-length lint rule. |
Sources/ProcessOutCheckout3DS/Sources/Service/POCheckout3DSService.swift |
Retain and reuse the transaction, enforce lifecycle checks, and centralize teardown. |
Scripts/Tests/Run.sh |
Restore the Checkout 3DS scheme to CI test runs. |
Review effort: Max
* Test concurrent authentication while the first call is in flight. * Rename not-cleaned transaction test to match the state it covers. * Match state rejection failures by message instead of accepting any error.
vitalii-vanziak-cko
approved these changes
Nov 5, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Summary
POCheckout3DSService.performChallengecalledcreateTransaction()a second time instead of reusing the transaction that produced the authentication request. Checkout'screateTransaction()closes the current transaction and creates a new one with a fresh SDK transaction ID and ephemeral key pair. The challenge therefore ran on a transaction that didn't match the AReq, so the ACS shouldn't be able to decrypt it. The regression came from the async rework in #375. Before that, one transaction was kept for both steps.TransactionContextand runs the challenge on it.PONetcetera3DS2Service:idle,authenticating,ready,challenging. There's nocleaningstate because Checkout's teardown is synchronous, so nothing could observe it. Overlapping calls and re-authentication withoutclean()are rejected. A challenge whosethreeDSServerTransIDdoesn't match the authentication is rejected.clean()during an in-flight operation is ignored with a warning.cleanUp(), as Netcetera does and as this service did before feat(POM-434): update Checkout3DS SDK #375. A failed authentication now tears down the SDK straight away.Tests
PONetcetera3DS2Servicestate tests to Checkout. They run against the real Checkout SDK and use only the public API. Request fixtures are decoded from JSON because the request types have no public initializers.ProcessOutCheckout3DSscheme toScripts/Tests/Run.sh, so these tests run in CI. It had been removed in feat(POM-533): card scanner denied permission alert #502.type_body_lengthforPONetcetera3DS2Service, which is now one line over the limit.