fix(connector): [WORLDPAYWPG] fix PSync/RSync response parsing - #2130
Open
pixincreate wants to merge 13 commits into
Open
fix(connector): [WORLDPAYWPG] fix PSync/RSync response parsing#2130pixincreate wants to merge 13 commits into
pixincreate wants to merge 13 commits into
Conversation
PSync and RSync returned RESPONSE_HANDLING_FAILED for every order.
The sync response body was deserialized directly into the
`#[serde(untagged)]` WorldpayxmlTransactionResponse. Untagged forces
serde's `deserialize_any`, whose buffered value loses XML sequence and
text semantics: `balance` buffers as a map rather than a sequence and
`<lastEvent>X</lastEvent>` buffers as `{"$text": "X"}`, which a
unit-variant enum cannot be built from. The `Payment` variant therefore
never matched any payload, and the all-optional webhook variant absorbed
everything and masked the real failure.
The enum is now a carrier that is never deserialized as untagged. A
concrete, element-driven shape is parsed instead, the `<paymentService>`
envelope is wrapped into `Payment` manually, and the order-notification
body is only used as a fallback — matching how hyperswitch handles this.
`WorldpayxmlWebhookResponse` also regains mandatory `OrderCode` and a
typed `PaymentStatus` so it can no longer match arbitrary input.
Also hardens the response model against Worldpay's evolving payloads:
- Adds the Settled, SentForAuthorisation, SentForFastRefund,
RefundRequested, RefundedByMerchant, QueryRequired, CancelReceived and
RefundReceived events, plus `#[serde(other)] Unknown` so an
unmodelled event no longer fails the whole response.
- Threads the previous status into the payment, mandate and refund
status mappers so an Unknown event logs a warning and retains the
status we already had instead of assuming a terminal one.
- Wraps previously mandatory response fields (AuthorisationId id,
ResultCode description, Balance accountType/amount, PaymentMethodDetail
card, CardResponse type) in Option, since Worldpay omits them
depending on the order journey.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ldpayxml-psync-response-parsing * feat/worldpayxml-wallet-decrypt-recurring: fix(connector): [WORLDPAYXML] add ErrorResponse observability fields chore(version): 2026.08.14.0 feat(observability): add typed connector request/response fields (#2036) fix(connectors): [tsys_transit] add support for psync and rsync (#2100) chore(version): 2026.08.13.1 fix(grabpay,maya): added superposition urls (#2118)
pixincreate
marked this pull request as ready for review
August 14, 2026 06:12
Auto-applied by CI: - cargo +nightly fmt --all - make -C sdk generate (if applicable) - make docs (if applicable) This commit was automatically generated by GitHub Actions.
…ldpayxml-psync-response-parsing * feat/worldpayxml-wallet-decrypt-recurring: chore(connector): [WORLDPAYXML] regenerate probe data, docs and examples fix(connector): [WORLDPAYXML] escape last_event before JSON parsing chore: auto-fix formatting and generated code # Conflicts: # crates/integrations/connector-integration/src/connectors/worldpayxml/transformers.rs
…arsing' into fix/worldpayxml-psync-response-parsing * origin/fix/worldpayxml-psync-response-parsing: chore: auto-fix formatting and generated code
…ldpayxml-psync-response-parsing * feat/worldpayxml-wallet-decrypt-recurring: fix(connector): read calida shop name from connector specific config (#2106)
…ldpayxml-psync-response-parsing * feat/worldpayxml-wallet-decrypt-recurring: test(connector): [WORLDPAYXML] declare the recurring suites in specs.json
Shubhodip900
approved these changes
Aug 14, 2026
…recurring' into fix/worldpayxml-psync-response-parsing * origin/feat/worldpayxml-wallet-decrypt-recurring: fix(connector): [WORLDPAYXML] fail token-less mandate setup, surface MIT declines
…recurring' into fix/worldpayxml-psync-response-parsing * origin/feat/worldpayxml-wallet-decrypt-recurring: revert(connector): [WORLDPAYXML] keep token-less mandate setup lenient
…recurring' into fix/worldpayxml-psync-response-parsing * origin/feat/worldpayxml-wallet-decrypt-recurring: refactor(connector): [WORLDPAYXML] address review feedback on 2122 # Conflicts: # crates/integrations/connector-integration/src/connectors/worldpayxml/transformers.rs
…recurring' into fix/worldpayxml-psync-response-parsing * origin/feat/worldpayxml-wallet-decrypt-recurring: refactor(connector): [WORLDPAYXML] convert the remaining two helpers to From impls
…recurring' into fix/worldpayxml-psync-response-parsing * origin/feat/worldpayxml-wallet-decrypt-recurring: refactor(connector): [WORLDPAYXML] convert the refund status mapping to a From impl # Conflicts: # crates/integrations/connector-integration/src/connectors/worldpayxml/transformers.rs
…recurring' into fix/worldpayxml-psync-response-parsing * origin/feat/worldpayxml-wallet-decrypt-recurring: docs(grace): require superposition URL registration + URL patching for new connectors (#2123) chore(version): 2026.08.14.1
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.
The bug
PaymentService/Get(PSync) andRefundService/Get(RSync) returnedRESPONSE_HANDLING_FAILEDfor every worldpayxml order — including plain card orders with no token, so this is independent of the recurring work in #2122 and predates it.The sync body was deserialized directly into
#[serde(untagged)] WorldpayxmlTransactionResponse. Untagged forces serde'sdeserialize_any, whose buffered value loses XML sequence and text semantics, so thePaymentvariant could never match any payload:balance: Option<Vec<_>>— a single<balance>buffers as a map, not a 1-element sequence.<lastEvent>CAPTURED</lastEvent>buffers as{"$text": "CAPTURED"}, which a unit-variant enum cannot be built from.WorldpayxmlWebhookResponse, whose fields were allOption, then matched unconditionally — it matches literally any input, including unrelated XML. Every real parse error was silently swallowed and resurfaced as a generic failure attransformers.rs:1699, naming neither the failing type nor the field.Proven in a standalone repro on the same quick-xml version: even the Authorize response that parses fine today resolves to the
Webhookvariant when routed through that enum. Authorize/Capture/Void/Refund work only because they are typed directly.The fix
Stop routing XML through an untagged enum.
WorldpayxmlTransactionResponseno longer derivesDeserialize; a hand-written impl parses a concrete, element-driven body, wraps a<paymentService>reply intoPaymentmanually, and only then falls back to the order-notification body. Concrete type first, enum as a manually-constructed carrier, nodeserialize_anyanywhere.WorldpayxmlWebhookResponsegains a requiredOrderCodeand a typedPaymentStatus— the all-Optionshape is what made it match everything.Response hardening, folded in
LastEvent: addedSettled,SentForAuthorisation,SentForFastRefund,RefundRequested,RefundedByMerchant,QueryRequired,CancelReceived,RefundReceived, plus#[serde(other)] Unknown. Without this PSync would have broken again the moment an order settled.previous_statusand have an explicitUnknownarm that logs a warning and retains the prior status instead of erroring or assuming a terminal state.Option(AuthorisationId.@id,ResultCode.@description,Balance.@accountType/amount,PaymentMethodDetail.card,CardResponse.@type).Expiredwas kept even though the reference implementation lacks it — dropping it would silently reroute a realEXPIREDevent intoUnknown/retain-previous, a behaviour regression.payout_connectors/worldpayxml/transformers.rsis touched only because its status mapper matches the same shared enum and would not otherwise compile.parse_last_eventis removed entirely — withWorldpayxmlLastEventnow deserialized as a typed field, the helper is dead code. (#2122 fixes an unsafe JSON interpolation in it for the interim.)Testing
Verified against the Worldpay sandbox. Clippy warning-free, no
unwrap/expect, no suppressions, no test files.RESPONSE_HANDLING_FAILEDCHARGED, amount returned<token>→ PSyncRESPONSE_HANDLING_FAILEDCHARGEDRESPONSE_HANDLING_FAILEDREFUND_PENDING(correct — Worldpay had not yet reflected it)The payload that used to fail now parses, including the
balancearray and<lastEvent>that brokedeserialize_any:An early PSync on a not-yet-available order returned Worldpay's
<error code="5">shape and correctly yieldedPENDINGrather than a hard failure.🤖 Generated with Claude Code