Skip to content

Upgrade to setono/quickpay-php-sdk ^1.1 and act on its release notes - #83

Merged
loevgaard merged 2 commits into
2.xfrom
chore/sdk-1.1
Aug 17, 2026
Merged

loevgaard merged 2 commits into
2.xfrom
chore/sdk-1.1

Conversation

@loevgaard

Copy link
Copy Markdown
Member

SDK 1.1.0 (released today) is additive — its UPGRADE.md has nothing to document — but its notes recommend things this gateway hand-rolled or was missing. Two commits, reviewable separately:

1. ^1.1, and use what it added

  • Operation::isApproved() / isOfType() / QP_STATUS_APPROVED are now what every Operations helper builds on; our own APPROVED_STATUS_CODE is gone. The SDK's definition is not pending AND 20000 — a pending operation is never approved, whatever its code says (test).
  • Shopsystem on the create request: ConvertPaymentAction identifies the integration as setono/payum-quickpay + the installed version (Composer's runtime API; composer-runtime-api ^2.0 declared, as the SDK does), so the manager and Quickpay support can see what created a payment. A consumer's own Convert (the Sylius plugin has one) can say something more specific.
  • Docs: the SDK's one-argument mapper cache is reachable through quickpay.client (new Client($key, synchronized: …, cache: new FileSystemCache($dir))) — no gateway option, no Valinor dependency. CLAUDE.md records what was adopted and what deliberately was not (the type-agnostic latestOperation()/hasPendingOperation(), the summed amount helpers).
  • Since 1.1 an empty request body (cancel) goes out as {}; the test asserts the new shape.

2. Convert finds or creates (the release notes' checkout recipe — and a real bug here)

Quickpay enforces order_id uniqueness per account: a second create is a 400 order_id already exists on another payment (verified live). The gateway builds the order id from the Payum payment number, and under Sylius that is the order number (CapturePaymentAction sets $payumPayment->setNumber($order->getNumber())) — so a customer who was declined, came back to the shop and paid again arrived at Convert with an order id Quickpay already had, and the retry died with a ValidationException.

ConvertPaymentAction now looks the order id up first (findByOrderId(), one GET per creating Convert; a model with an id makes no request as before):

Quickpay has under that order id… Convert
nothing creates, as before
a payment nobody has paid — no approved operation (created/never completed, declined, authorize in flight), same currency adopts it; the entry-point actions treat a declined attempt like a fresh payment and create a new link
a payment with an approved operation (authorized/captured/refunded/cancelled) LogicException naming it — never adopts money silently: it is either this order's earlier payment that really was paid (carry its quickpayPaymentId over) or another environment's under a prefix that should not be shared (give each its own order_prefix)
a payment in another currency LogicException — the payment is what Quickpay charges in

Verified live through the real gateway: an initial e2e payment was adopted (no create issued); an authorized one, a captured/refunded one and a same-order-id payment in another currency were refused with exactly those messages.

Tests for every row (+ the integration test now expects the lookup); README (flow + "Retrying a checkout"), docs/UPGRADE-2.0.md (new section), CLAUDE.md. composer all, the dependency analyser and composer normalize/validate green (369 tests).

SDK 1.1.0 (2026-08-17) is additive, but several of its additions are
things this gateway hand-rolled or should say:

- Operation::isApproved() (not pending AND status 20000) and isOfType()
  are now what every Operations helper builds on; the package's own
  APPROVED_STATUS_CODE constant is gone in favour of the SDK's
  Operation::QP_STATUS_APPROVED. A pending operation is never approved,
  whatever its code says.
- ConvertPaymentAction sends Quickpay's `shopsystem` on the create
  request — the integration's name and installed version (Composer's
  runtime API) — so the manager and Quickpay support can tell what
  created a payment. A consumer's own Convert action can say more.
- Docs: the SDK's one-argument mapper cache is reachable through the
  quickpay.client option (build the client with `cache:`), so the
  package needs no option of its own nor a Valinor dependency.
- Since 1.1 an empty request body (cancel) goes out as `{}`; the test
  asserts the new shape.

Not adopted on purpose: the SDK's type-agnostic latestOperation() /
hasPendingOperation() and its summed amount helpers — the actions need
the per-type, last-approved views Operations provides.
… order id

Quickpay enforces order_id uniqueness per account: a second create under
an existing order id is a 400 "order_id already exists on another
payment" (verified live). The gateway builds the order id from the Payum
payment number, and under Sylius that is the ORDER number (its
CapturePaymentAction sets it so), so a customer who was declined, came
back to the shop and paid again arrived at Convert with an order id
Quickpay already had — and the retry died with a ValidationException,
for the most ordinary of reasons. SDK 1.1's findByOrderId() is the
recipe for exactly this.

ConvertPaymentAction now looks the order id up first. A payment nobody
has paid — no approved operation: created but never completed, declined,
an authorize still in flight — in the same currency is adopted, and the
checkout continues on it (the entry-point actions treat a declined
attempt like a fresh payment and create a new link). A payment WITH an
approved operation is never adopted silently: it is either this order's
earlier payment that really was paid, or another environment's under a
prefix that should not be shared, and either way that is the shop's
call, so it is a LogicException naming it rather than a claim of money.
Another currency is refused too — the payment is what Quickpay charges
in.

Verified live against the real API: an initial e2e payment is adopted
without a create; an authorized one, a captured/refunded one and a
same-order-id payment in another currency are refused with the messages
above.
@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 98.60%. Comparing base (2081039) to head (889736b).

Additional details and impacted files
@@             Coverage Diff              @@
##                2.x      #83      +/-   ##
============================================
+ Coverage     98.52%   98.60%   +0.07%     
- Complexity      215      220       +5     
============================================
  Files            20       20              
  Lines           542      572      +30     
============================================
+ Hits            534      564      +30     
  Misses            8        8              

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

@loevgaard
loevgaard merged commit 0a0b832 into 2.x Aug 17, 2026
21 checks passed
@loevgaard
loevgaard deleted the chore/sdk-1.1 branch August 17, 2026 11:57
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.

1 participant