Skip to content

Guard every action against a missing quickpayPaymentId - #60

Merged
loevgaard merged 1 commit into
2.xfrom
fix/missing-payment-id-guards
Aug 10, 2026
Merged

Guard every action against a missing quickpayPaymentId#60
loevgaard merged 1 commit into
2.xfrom
fix/missing-payment-id-guards

Conversation

@loevgaard

Copy link
Copy Markdown
Member

Fixes robustness finding R3 of #57.

Problem

The guards were inconsistent: StatusAction, SyncAction and ConfirmPaymentAction handled a missing quickpayPaymentId explicitly, but AuthorizeAction, CaptureAction, RefundAction and CancelAction did not — (int) $model['quickpayPaymentId'] on a missing key is (int) null = 0, so the call went out as an operation on /payments/0 and came back as a NotFoundException after a network round trip, blaming a payment id nobody ever set. AuthorizeAction even ran validateNotEmpty() on four other keys but not this one.

Fix

New Details::paymentId() helper (mirroring the existing Amounts pattern) reads the id in one place and is used by all seven read sites:

  • missing / null / non-numeric / non-positive → a clear LogicException ("The payment has not been created at Quickpay …") before any HTTP happens — this also hardens the three previously guarded actions, where a garbage id ('abc') still slipped through the loose cast as 0;
  • a numeric string is accepted — the shape a serialization round trip in the consumer's storage may produce;
  • AuthorizeAction resolves the id before minting a notify token, so no token is created for a payment that does not exist.

StatusAction's markNew() and SyncAction's no-op for an absent key are unchanged — absence is meaningful there by design.

Tests

Per-action guard tests (throw + zero HTTP requests) for Authorize/Capture/Refund/Cancel, plus a dedicated DetailsTest covering the accepted and rejected shapes.

StatusAction, SyncAction and ConfirmPaymentAction already guarded the
missing-id case; AuthorizeAction, CaptureAction, RefundAction and
CancelAction did not — (int) $model['quickpayPaymentId'] on a missing key
is (int) null = 0, so the call went out as an operation on /payments/0
and came back as a NotFoundException after a network round trip, blaming
a payment id nobody ever set.

The new Details::paymentId() helper reads the id in one place: missing,
non-numeric or non-positive values throw a clear LogicException before
any HTTP happens (also catching a garbage id where the loose (int) cast
used to send 0), and a numeric string — the shape a serialization round
trip in the consumer's storage may produce — is accepted.
@codecov

codecov Bot commented Aug 10, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.23810% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 97.23%. Comparing base (6122f5f) to head (3cffb55).

Files with missing lines Patch % Lines
src/Details.php 90.90% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##                2.x      #60      +/-   ##
============================================
- Coverage     97.43%   97.23%   -0.20%     
- Complexity      126      132       +6     
============================================
  Files            14       15       +1     
  Lines           351      362      +11     
============================================
+ Hits            342      352      +10     
- Misses            9       10       +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.

@loevgaard
loevgaard merged commit e34d31e into 2.x Aug 10, 2026
18 of 21 checks passed
@loevgaard
loevgaard deleted the fix/missing-payment-id-guards branch August 10, 2026 14:14
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