Skip to content

Surface Quickpay's fraud signals - #139

Merged
loevgaard merged 3 commits into
2.xfrom
fraud-signals
Aug 24, 2026
Merged

Surface Quickpay's fraud signals#139
loevgaard merged 3 commits into
2.xfrom
fraud-signals

Conversation

@loevgaard

Copy link
Copy Markdown
Member

Closes #132.

Three surfaces for the fraud data the SDK already models (Metadata::$fraudSuspected, PaymentsQuery's fraudSuspected filter):

Admin badge

The operation history panel shows a red Fraud suspected label next to the existing test-mode badge, read off the fetched payment's metadata. Translated in all 16 locales.

Opt-in capture guard

setono_sylius_quickpay:
    fraud:
        block_capture: true   # default false

When enabled, PaymentProcessor consults the new Fraud\FraudChecker before the automatic capture on the complete transition and skips the capture with a warning when Quickpay reports the payment as fraud suspected — the transition itself proceeds (mirroring how a failed cancel is handled), leaving the payment for manual review in the Quickpay manager.

Design choices:

  • Fails open on every unanswerable case — no Quickpay payment id, no api key, Quickpay unreachable — so a broken check never blocks the payment flow.
  • Off by default because the check costs one extra Quickpay API call per automatic capture; the config info says so.
  • The checker is its own FraudCheckerInterface seam (with interface alias, per the repo convention), so the processor's tests stay cheap.

Reconcile report mode

bin/console setono:sylius-quickpay:reconcile-payments --fraud-suspected --since="7 days"

Unlike reconciliation (which walks local pending payments), this asks Quickpay directly: every configured Quickpay gateway's account is queried for payments flagged in the period, regardless of local state, and printed as a table (gateway, Quickpay id, order id, state, created, test mode). Report only — nothing is transitioned or persisted. Exit code fails only on API errors.

Tests

The SDK's endpoint classes are final and cannot be doubled, so the new tests run the real SDK client against a canned-response PSR-18 stub (tests/Quickpay/FixedResponseHttpClient) — covering the flagged/clean/no-id/no-key/unreachable paths of the checker and the report mode end to end through the command. Processor tests pin: capture skipped on suspicion, capture proceeds when clean, and the checker is never consulted when the guard is off.

The report mode was also verified live against the real Quickpay API (empty result on an account with no flagged payments).

README (Fraud signals section), UPGRADE-2.0.md, and CLAUDE.md updated.

Three surfaces for the fraud data the SDK already models:

- The admin operation history panel shows a "Fraud suspected" badge (translated
  in all 16 locales) next to the test-mode badge, read off the payment's
  metadata.
- An opt-in fraud.block_capture config flag makes PaymentProcessor consult the
  new Fraud/FraudChecker before the automatic capture on the complete
  transition and skip it with a warning when Quickpay reports the payment as
  fraud suspected - the transition itself proceeds, leaving the payment for
  manual review. The checker fails open on every unanswerable case (no
  Quickpay payment, no api key, Quickpay unreachable), so it never blocks the
  payment flow, and it is off by default because it costs one extra API call
  per automatic capture.
- setono:sylius-quickpay:reconcile-payments gains a --fraud-suspected report
  mode that queries each configured gateway's account directly for flagged
  payments in the period - report only, nothing is transitioned.

The final SDK endpoint classes cannot be doubled, so the new tests run the real
SDK client against a canned-response PSR-18 stub (tests/Quickpay/
FixedResponseHttpClient), and the report mode was verified live against the
Quickpay API.

Closes #132
@codecov

codecov Bot commented Aug 24, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.56098% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 94.27%. Comparing base (b47fe4b) to head (567dd08).
⚠️ Report is 1 commits behind head on 2.x.

Files with missing lines Patch % Lines
src/Controller/Admin/PaymentOperationsAction.php 75.00% 1 Missing ⚠️
src/Quickpay/ApiKeyResolver.php 88.88% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##                2.x     #139      +/-   ##
============================================
+ Coverage     93.86%   94.27%   +0.41%     
- Complexity      205      226      +21     
============================================
  Files            25       27       +2     
  Lines           766      839      +73     
============================================
+ Hits            719      791      +72     
- Misses           47       48       +1     

☔ 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.

Comment thread src/Fraud/FraudChecker.php Outdated
Comment thread src/Fraud/FraudChecker.php Outdated
- FraudChecker reads the payment id through the gateway library's Details
  helper; a present but unusable id lands in the same fail-open catch as an
  unreachable Quickpay, since either way the fraud question cannot be answered
- The api_key/apikey resolution lived in three places (FraudChecker,
  PaymentOperationsAction, the reconcile fraud report); it is now the
  Quickpay\ApiKeyResolver helper, the one place that knows about the pre-2.0
  option name
@loevgaard
loevgaard merged commit 3caa0ff into 2.x Aug 24, 2026
29 of 30 checks passed
@loevgaard
loevgaard deleted the fraud-signals branch August 24, 2026 10:01
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.

Surface Quickpay's fraud signals: admin badge, capture guard, reconcile filter

1 participant