Skip to content

Per-operation callback URL on authorize/capture/refund/cancel; extra request headers; Link::$autoCapture - #26

Merged
loevgaard merged 4 commits into
1.xfrom
feat/operation-callback-url
Aug 17, 2026
Merged

Per-operation callback URL on authorize/capture/refund/cancel; extra request headers; Link::$autoCapture#26
loevgaard merged 4 commits into
1.xfrom
feat/operation-callback-url

Conversation

@loevgaard

@loevgaard loevgaard commented Aug 17, 2026

Copy link
Copy Markdown
Member

Closes #22.

Why

Quickpay POSTs the callback of an API-issued operation to the account-wide callback URL (manager → Settings → Integration) — empty by default — not to the callback_url set on the payment link. So an integration that only ever set the link's URL never hears about its captures, refunds and cancels. The swagger lists a QuickPay-Callback-Url request header on authorize, capture, refund, cancel (and renew/session/fraud-report) that routes that one operation's callback; #22 verified it live.

What

  • PaymentsEndpoint::authorize() / capture() / refund() / cancel() take ?string $callbackUrl = null (after $synchronized, so named args read capture($id, $req, callbackUrl: $url)). Client::CALLBACK_URL_HEADER names the header.
  • ResourceEndpoint::postOperationWithHeaders() (new, protected) does the sending; postOperation() keeps its signature and delegates — so no change for the overridable method.
  • Low-level helpers accept extra request headers: get($uri, $query, $headers), post/put/patch($uri, $body, $headers), delete($uri, $headers) — so unmodeled operations (renew, …) can send it too. The SDK's own headers (Authorization, Accept-Version, Accept, User-Agent) always win.
  • Link::$autoCapture (?bool) and $autoCaptureAt (?string) typed (swagger PaymentLink: boolean / ISO-8601 string; kept as string to be safe with the strict mapper).
  • README: a "Where does the callback for a capture/refund/cancel go?" paragraph with examples, and a note in "Handling callbacks robustly". examples/e2e/operate.php now passes the listener's /callback as the per-operation URL when QUICKPAY_CALLBACK_BASE (or --callback-base=) is set — which is what makes its "watch each callback land" claim actually true.
  • CHANGELOG entry added to the current release list (so it rides in 1.1.0 if merged before Prepare the 1.1.0 changelog #24 — I'll update the draft notes; if you'd rather ship it in 1.2.0, say so and I'll move the line).

BC — please look at this one

The new Roave check flagged the optional $headers parameter on the five ClientInterface methods (an implementor with the old signature would fatal). Rather than contort the plumbing, I kept the interface change and added .roave-backward-compatibility-check.xml baselining exactly those five lines, with the reasoning inline: Client is the interface's only implementation, consumers type-hint/mock it and are not expected to implement it — now stated in the interface docblock ("may gain optional parameters in minor releases"). Nothing downstream implements it (checked payum-quickpay). If you'd rather keep the interface frozen, the alternative is a Client-only method + instanceof in the endpoints, which I think is worse — but it's your call, and this commit is separate (1f831e9).

Tests: header sent on each of the four operations (data provider), absent by default, combined with ?synchronized, on all five low-level helpers, SDK headers winning, Link fields present/absent.

@codecov

codecov Bot commented Aug 17, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.14%. Comparing base (a32317c) to head (2881758).

Additional details and impacted files
@@            Coverage Diff            @@
##                1.x      #26   +/-   ##
=========================================
  Coverage     99.13%   99.14%           
- Complexity      222      226    +4     
=========================================
  Files            27       27           
  Lines           576      582    +6     
=========================================
+ Hits            571      577    +6     
  Misses            5        5           

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

string $action,
Payload|array $request,
?bool $synchronized,
array $headers,

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just add $headers to postOperation?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — postOperation() now takes array $headers = [] directly and postOperationWithHeaders() is gone (PaymentsEndpoint builds the header via a tiny private callbackUrlHeader()). The split only existed to keep the BC check green; with the red-once decision below there was no reason for it.

Comment thread .roave-backward-compatibility-check.xml Outdated
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8" ?>

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this file again. Just allow this PR to be red this one time

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed (and the interface docblock no longer references it). The BC check will report the six optional-parameter additions (ClientInterface::get/post/put/patch/delete, ResourceEndpoint::postOperation) as breaks on this PR — accepted as red this once, per your call.

…request headers; Link::$autoCapture

Quickpay POSTs the callback of an API-issued operation to the ACCOUNT-WIDE
callback URL (empty by default), not to the payment link's callback_url — so
integrations that only set the link's URL never hear about their captures,
refunds and cancels. The swagger lists a QuickPay-Callback-Url request header
on authorize/capture/refund/cancel (and renew/session/fraud-report) that routes
that one operation's callback; verified live in #22.

- PaymentsEndpoint::authorize()/capture()/refund()/cancel() take
  ?string $callbackUrl (after $synchronized); ResourceEndpoint::postOperation()
  turns it into the header. Client::CALLBACK_URL_HEADER names it.
- ClientInterface/Client: get()/post()/put()/patch()/delete() accept an
  optional array<string,string> $headers (the SDK's own headers always win),
  so unmodeled operations can send it too.
- Link::$autoCapture (?bool) and $autoCaptureAt (?string) typed.
- README: "where does the callback for a capture/refund/cancel go"; callback
  best practices note. examples/e2e/operate.php passes the listener's
  /callback when QUICKPAY_CALLBACK_BASE (or --callback-base=) is set.

Closes #22.
…ce header params

The Roave check flagged the optional parameters added to ClientInterface's
helpers and to the overridable ResourceEndpoint::postOperation():

- postOperation() keeps its signature and delegates to a new protected
  postOperationWithHeaders(); PaymentsEndpoint uses the latter (empty header
  values are skipped, so callers can pass the callback url unconditionally).
- The ClientInterface additions stay: Client is its only implementation and
  consumers type-hint/mock it rather than implement it (now documented on the
  interface). .roave-backward-compatibility-check.xml baselines exactly those
  five parameter additions with the reasoning inline; export-ignored.
Keeps postOperation() on the hot path (and covered); the with-headers variant
is only used when there is a header to send.
Per review: no separate postOperationWithHeaders() — postOperation() gains an
optional array $headers. And no .roave-backward-compatibility-check.xml: the
optional-parameter additions on ClientInterface / postOperation() are
accepted as a one-time red BC check for this PR (they only affect
implementors of the mock-only interface / subclassers of the internal base).
@loevgaard
loevgaard force-pushed the feat/operation-callback-url branch from c071e81 to 2881758 Compare August 17, 2026 12:41
@loevgaard
loevgaard merged commit ec2fd1b into 1.x Aug 17, 2026
35 of 36 checks passed
@loevgaard
loevgaard deleted the feat/operation-callback-url branch August 17, 2026 12:45
loevgaard added a commit that referenced this pull request Aug 17, 2026
Move the #26 / #27 entries out of 1.1.0 into a 1.2.0 section (Added / Changed /
BC notes) and add the compare links. 1.1.0 stays the DX-review release
(tagged before #27/#26 landed); 1.2.0 is the two payum-quickpay follow-ups.
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.

Per-operation callback url: send QuickPay-Callback-Url on capture/refund/cancel (and model Link::$autoCapture)

1 participant