Skip to content

3.2.0 - path identifier encoding, boolean window fields, response-shape guards

Choose a tag to compare

@tomsommer tomsommer released this 07 Sep 17:57
· 4 commits to master since this release

Three defects found by an external audit of 3.0.0. All were inherited from upstream, and all are reproduced by tests that fail if the fix is reverted.

Identifiers could change the action

Identifiers were interpolated into the request path unencoded everywhere except getTransaction(). A value containing ? turns the intended suffix into a query string:

refundTransaction("1234/capture?", 500)
  → POST /v1/transaction/1234/capture?/refund

OnPay reads that as a capture, and a capture accepts the body a refund sends. The same trick turned a subscription cancel into an authorize. Every identifier that becomes a path segment is now encoded.

If your identifiers come from OnPay itself this was not reachable, but the contract was wrong.

setTestMode(false) broke every live payment

http_build_query() renders false as 0 when the window signs itself, but the raw bool renders as an empty string in a form field. Signed and posted therefore disagreed:

signed:   ...&onpay_reference=r&onpay_testmode=0
rendered: ...&onpay_reference=r&onpay_testmode=

so OnPay rejected the window for a bad HMAC. Anyone calling setTestMode($isTest) with false — the natural way to write it — had every live payment rejected. Booleans are now normalised to the string the query builder would have produced, which leaves the signature unchanged and fixes the rendering.

Malformed responses raised TypeError instead of ApiException

A response that was valid JSON of the wrong shape — an empty object, a bare string, a missing data member, a non-string error message — reached a DTO constructor and surfaced as a TypeError. Callers catch ApiException, so a surprising response became a fatal rather than a handled failure. declare(strict_types=1), added in 2.1.0, made this sharper than it had been.

ApiClient now rejects a success body that is not an object and ignores a non-string error message, and the new ResponseParser turns the envelope into a typed failure in one place rather than eleven. Absent links are still tolerated, since they are genuinely optional.