Skip to content

Reach Quickpay through one plugin-owned client with one HTTP policy, and return verdicts from gateway checks - #175

Draft
loevgaard wants to merge 46 commits into
2.xfrom
quickpay-client-module
Draft

loevgaard wants to merge 46 commits into
2.xfrom
quickpay-client-module

Conversation

@loevgaard

@loevgaard loevgaard commented Nov 9, 2026 •

Copy link
Copy Markdown
Member

Closes #165. Closes #166, closes #167, closes #168, closes #169, closes #170, closes #171, closes #172, closes #173, closes #174.

Draft while the tickets of #165 land on this integration branch. Decisions: ADR 0003.

… switched-off operations alone

Completing a payment whose status check maps to anything but authorized
or completed now throws NoAuthorizationToCapture after the status check,
so a never-paid payment no longer falls into Sylius' token-less Convert
and a declined one no longer gets its payment link replaced. The fraud
guard runs only for an authorization about to be captured.

A switched-off operation returns before the gateway is built or the
status is checked, so an unusable gateway configuration no longer blocks
its transition. Building the gateway moved into each operation: capture
and refund abort on an unusable configuration, cancel logs a warning and
proceeds, whichever exception the configuration surfaces as.
CLAUDE.md's state machine section gives the order complete runs its
checks in and how each operation treats an unusable gateway
configuration; UPGRADE-2.0.md's alpha section and the README describe
the behaviour change.
…nt provider

The extension prepends setono_sylius_quickpay.http_client onto framework.http_client: base URI
https://api.quickpay.net, 10 s idle, 10 s in total, no retries (ADR 0003). An application overrides
single options under the same name in its own framework config.

QuickpayClientProviderInterface::forGatewayConfiguration() builds a new SDK client per call from the
gateway configuration's typed options (api key, synchronized), on a PSR-18 adapter over that scoped
client and with the SDK's mapper cache, and throws UnusableGatewayConfiguration for an unusable one.
…below it in tests

The gateway factory builder sets payum-quickpay's quickpay.client gateway option to a closure that
builds each gateway's client through the client provider, from the gateway's own config, and the
client factory builds its clients on the same scoped client. So gateway operations and the plugin's
own calls follow one HTTP policy.

In the test env the scripted Quickpay is the framework's mock_response_factory, below the scoped
client, replacing ScriptedGatewayFactoryBuilder and ScriptedClientFactory: the real provider, SDK
clients and policy run in every functional test, and the double records each request's options.
@codecov

codecov Bot commented Nov 9, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 99.57265% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 96.47%. Comparing base (02ff491) to head (63d03ed).
⚠️ Report is 1 commits behind head on 2.x.

Files with missing lines Patch % Lines
src/Quickpay/GatewayCheck/GatewayChecks.php 97.95% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##                2.x     #175      +/-   ##
============================================
+ Coverage     96.17%   96.47%   +0.29%     
- Complexity      337      359      +22     
============================================
  Files            36       38       +2     
  Lines          1177     1275      +98     
============================================
+ Hits           1132     1230      +98     
  Misses           45       45              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

…rows

The gateway's quickpay.client closure reads the configuration through the client provider before
payum-quickpay reads it, so building the gateway now throws the plugin's UnusableGatewayConfiguration
instead of payum-quickpay's InvalidGatewayOptions. Payum passes no gateway name with the config, so
its message names "a Quickpay gateway" instead of an empty name.

Tests pin it: both callback endpoints answer a 500 for a payment on an unusable configuration and
change nothing, and the reconcile command counts such a payment as errored and goes on.
README: an HTTP policy section with the scoped client's name and defaults, an override example, and
a GET-only retry_failed example warning that retrying writes can capture or refund twice.
UPGRADE-2.0.md: the scoped client and the client provider, the builder's and the client factory's
changed constructors, the one 10 s policy, and the exception building an unusable gateway throws.
CLAUDE.md: the client provider and policy, and the functional suite's new seam.
…it stopped it

The report gets each gateway's SDK client from the client provider, so it follows the one HTTP policy. Once it
holds --limit payments, it says it stopped there and names every gateway it didn't query, instead of skipping
them silently. Reaching the limit doesn't fail the run.
…e validation

The gateway checks module (Quickpay\GatewayCheck\GatewayChecksInterface) runs
the api key check on a client from the client provider and classifies
Quickpay's answers once: /ping accepted passes; a /ping refused with 401/403
is decided by a one-item /payments read, which passes with a note or fails;
any other error is undetermined, with Quickpay's error as the reason.

Save validation and the doctor branch on the verdict's outcome instead of
catching SDK exceptions. An undetermined api key is logged at info level on
save. The doctor takes the client for its agreement and live checks from
the provider. The api key verifier and its services are removed.
…nothing uses the client factory

With the doctor on the client provider, nothing injects the client factory, so the
container drops its service and the functional tests could no longer fetch it. The
policy test's plugin call is now the api key gateway check, and the harness proof
reads a Quickpay payment through the provider.
The doctor's --live probe moves into the gateway checks as
GatewayChecksInterface::paymentLinkPermission(): it creates a money-less
test payment and puts a link on it, failing on a 401/403 from either step
and leaving the verdict undetermined on any other Quickpay error. The link
is deleted best effort, and once the test payment exists the verdict's
note names it (id and order id). The test order id keeps the gateway's
order prefix plus "dr" and six random hex digits.

doctor --live prints the verdict and its note through report(), and now
warns that it skipped the probe when the api key didn't pass. Its unit
tests double the verdict; the request-level cases live in
GatewayChecksTest, at the HTTP seam. QueuedResponsesHttpClient lost its
last user and is removed.
# Conflicts:
#	src/Quickpay/GatewayCheck/GatewayChecks.php
#	src/Quickpay/GatewayCheck/GatewayChecksInterface.php
#	tests/Unit/Command/DoctorCommandTest.php
#	tests/Unit/Quickpay/GatewayCheck/GatewayChecksTest.php
…ient provider or the gateway checks

ClientFactoryInterface, ClientFactory, their service and alias, and the factory's unit test
go. So does tests/Quickpay/FixedResponseHttpClient, which nothing used any more. UPGRADE-2.0.md
lists the factory as removed, replaced by QuickpayClientProviderInterface, instead of as a
changed constructor. CLAUDE.md and the harness test's docblock no longer name the factory, and
the harness docblock no longer counts the doctor as a direct user of the client provider.
…the alpha upgrade notes

Symfony copies framework.http_client.default_options.retry_failed onto a scoped client only
when the same framework block (file, and when@ section) configures that scoped client, and only
when it is given as settings. So following the README's own override example in the file that
sets app-wide retries turns them on for Quickpay, POST requests included. The README's HTTP
policy now says so and how to keep the plugin's client off them; CLAUDE.md and UPGRADE-2.0.md
point to it.

UPGRADE-2.0.md's alpha section now introduces the gateway configuration module and the client
provider next to the reconciliation module, says the gateway checks and the fraud report build
their clients through the provider, and lists the doctor's new --live output. The README's HTTP
policy counts the agreement check on save among the plugin's own requests.
…Quickpay across tests

The anonymous recording logger repeated in five tests is tests/Log/RecordingLogger, the mapper cache directory
setup of the two unit tests that build the real client provider is the TemporaryCacheDir trait, and the gateway
checks' unit test scripts Quickpay with ScriptedHttpClient instead of re-implementing it. ScriptedHttpClient now
also takes a MockResponse, for what a PSR-7 response can't script: no answer, or one that breaks off.
…eaks off undetermined

GatewayChecks::ask() sends one request of a check and maps Quickpay's answer, so each check states only the
verdicts for an accepted request, a refusal and, for the agreement, a 404. Any other error is undetermined:
the SDK's QuickpayException, and now also the PSR-7 body's \RuntimeException when the connection drops after
Quickpay's headers arrived, which the SDK reads outside its transport error handling and lets through. A body
that isn't JSON or doesn't map already arrives as a QuickpayException.
…ver the api key's verdict

Without an agreement id there is nothing to ask Quickpay, so the doctor prints alpha.2's ✔ line again instead
of skipping it when Quickpay didn't accept the api key; only an agreement id waits for an accepted api key.
checkAgreement() reads the agreement id from the gateway configuration it already gets.
…atewayName

The api key and agreement verdicts go through one helper that refuses a failed check at its field and logs an
undetermined one. Its log context names the gateway under gatewayName, the key the operations panel's warning
uses.
The doctor's --live option, its hint and its skipped line say check where they said probe, and its gateway check
warnings and the api key's /payments note say check where they said verify. Save validation logs an undetermined
check as accepted with its check undetermined, not as accepted unverified. The docs follow.
…sableGatewayConfiguration only

QuickpayGatewayTest pins it, so the cancel comment, the money operation test and CLAUDE.md stop allowing for
payum-quickpay's InvalidGatewayOptions, and the reconciler's operations declare UnusableGatewayConfiguration.
UnusableGatewayConfiguration's message helper is describeGateway().
…first

Nothing orders the gateway configurations all() returns, so the test reads which gateway's api key the one
query carried and expects the other gateway to be named as unqueried.
… id or with a rejected api key

Through the real payment method form, saving without an agreement id asks Quickpay only about the api key, and
saving with an api key Quickpay rejects reports that one error and asks nothing about the agreement.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment