fix(core): allow Authorize without payment_method on the 3DS return leg - #2126
Closed
shuklatushar226 wants to merge 1 commit into
Closed
fix(core): allow Authorize without payment_method on the 3DS return leg#2126shuklatushar226 wants to merge 1 commit into
shuklatushar226 wants to merge 1 commit into
Conversation
Connectors that finalise 3-D Secure through the Authorize flow (Ilixium, NMI, Airwallex, Nexixpay, ...) are re-invoked after the ACS posts back to `TermUrl`. At that point the caller no longer holds the raw instrument, so Hyperswitch sends `payment_method_data: None` and omits the proto field. The Authorize handler required `payment_method` unconditionally and rejected the call with `InvalidArgument: Missing required field: payment_method` before any connector code ran, making 3DS completion impossible for every such connector. `payment_method` is now required only when this is not a redirect-return leg. The leg is identified by `redirection_response`, the same signal the connectors themselves key off (`request.redirect_response.is_some()`). A plain authorize with no `payment_method` is still rejected exactly as before. `PaymentsAuthorizeData::payment_method_data` is not optional, so the absent instrument is modelled explicitly as `PaymentMethodData::NoInstrumentAfterRedirect` rather than substituted with a default that a connector could mistake for a payable instrument. Every existing match on `PaymentMethodData` now handles the new variant through its existing unsupported-payment-method rejection, so no connector's behaviour changes; adding the variant is compiler-enforced, so a connector that later wants to accept the leg has to opt in deliberately. Verified end to end against a mock Ilixium server: HS /payments -> UCS -> `POST /direct/auth`, ACS challenge, complete-authorize -> `POST /direct/threedcomplete`, payment `succeeded`. The no-3DS authorize and the missing-`payment_method` rejection were re-checked and are unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Contributor
Author
shuklatushar226
deleted the
fix/authorize-3ds-completion-without-payment-method
branch
August 13, 2026 20:19
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.
Problem
PaymentService::authorizerequiredpayment_methodunconditionally:Connectors that finalise 3-D Secure through the Authorize flow — Ilixium, NMI, Airwallex,
Nexixpay and others that key off
request.redirect_response.is_some()— are re-invoked afterthe ACS posts back to
TermUrl. At that point the caller no longer holds the raw instrument, soHyperswitch sends
payment_method_data: Noneand omits the proto field (it is optional in boththe proto and Hyperswitch's
CompleteAuthorizeData).UCS therefore rejected the completion call with
InvalidArgument: Missing required field: payment_methodbefore any connector code ran, making 3DS completion impossible for everyconnector that uses this pattern.
Fix
payment_methodis now required only when the call is not a redirect-return leg. The leg isidentified by
redirection_response, the same signal the connectors themselves already use.A plain authorize with no
payment_methodis still rejected exactly as before.PaymentsAuthorizeData::payment_method_datais not optional, so the absent instrument ismodelled explicitly as a new
PaymentMethodData::NoInstrumentAfterRedirectvariant rather thansubstituted with a default (e.g.
MandatePayment) that a connector could mistake for a payableinstrument.
Adding the variant is compiler-enforced: all 51 existing matches on
PaymentMethodDatanowhandle it through their existing unsupported-payment-method rejection arm, so no connector's
behaviour changes and a connector that later wants to accept this leg has to opt in deliberately.
That accounts for the file count — the connector diffs are one added pattern alternative each.
PaymentsAuthorizeData::foreign_try_fromderivedpayment_method_typefrom the same field andhad the same unconditional gate; it now returns
Nonefor the instrument-less leg only (matchedon
NoInstrumentAfterRedirect), and errors as before otherwise.Verification
Driven end to end against a mock Ilixium server (Hyperswitch → UCS → mock):
POST /payments→requires_customer_action→ ACS challenge → complete-authorize →mock received
POST /platform/ili/direct/threedcomplete(merchant digest verified) → HSpayment status
succeeded. This call was rejected withInvalidArgumentbefore the fix.PaymentService/Authorizewithpayment_methodomitted andredirection_responsepresent now reaches the connector and returnsCHARGED.succeeded.Authorizewith nopayment_methodand noredirection_responsestill fails with
InvalidArgument: Missing required field: payment_method.cargo check --workspace --all-targets,cargo fmt --allandcargo clippyon the touchedcrates are clean.
Context
Found while integrating the Ilixium connector (#2125). Raised separately because it is a
core-flow change that unblocks 3DS completion for every UCS connector using this pattern, not
just Ilixium.
🤖 Generated with Claude Code