Skip to content

fix(woocommerce): resolve checkout attempts from order status, not payment_complete - #65

Merged
cport1 merged 1 commit into
mainfrom
fix/60-offline-gateway-attempts
Jul 30, 2026
Merged

fix(woocommerce): resolve checkout attempts from order status, not payment_complete#65
cport1 merged 1 commit into
mainfrom
fix/60-offline-gateway-attempts

Conversation

@cport1

@cport1 cport1 commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Closes #60.

woocommerce_payment_complete is not universal — COD, BACS and Cheque never fire it, so on a store using only those gateways no attempt row was ever closed and the #52 card-testing fix did not apply at all. Every genuine order kept counting toward card testing and the 5/hour velocity limit.

Resolution now comes from woocommerce_order_status_changed, which every gateway reaches:

Status Attempt
processing, completed, on-hold, refunded closed as a sale
failed, cancelled, pending keeps counting — this is the card-testing signal
anything else keeps counting (degrades safe)

on-hold is in the accepted set on purpose: it is where BACS and Cheque leave a legitimate order awaiting funds, and nobody tests stolen cards by promising a bank transfer.

Two adjacent defects fixed in the same path, both of which independently left rows open:

  • track_payment()/track_failure() keyed their UPDATE on the current request's IP. Payment resolution arrives on an async gateway callback where that is the gateway's address or empty, so the UPDATE matched nothing. Now keyed on order_id alone.
  • get_recent_attempts() is NULL-safe now — NULL <> 'success' is NULL in MySQL, so a status-less row was silently dropped rather than counted.

The test earned its place immediately: the first version used ltrim($status, 'wc-'), which takes a character list rather than a prefix, so it ate the leading c of both completed and cancelled. The suite failed on completed and I replaced it with an explicit prefix strip.

80/80 tests (5 new), no new phpcs errors, phpstan unchanged (the one error is the pre-existing class-webdecoy-updater.php issue).

…yment_complete (#60)

The card-testing and velocity filters exclude attempts that became a real order.
That was decided solely by `woocommerce_payment_complete`, which Cash on
Delivery, Direct Bank Transfer and Cheque never fire — they move the order to
processing or on-hold and return. On a store using only those gateways no attempt
row was ever closed, so `status <> 'success'` excluded nothing and every genuine
order kept counting toward card testing and the 5/hour velocity limit. The #52
fix did not apply there at all.

Now resolved from `woocommerce_order_status_changed`, which every gateway reaches.
processing / completed / on-hold / refunded close the attempt as a sale; failed,
cancelled and pending keep counting, because those are the card-testing signal.
`on-hold` is deliberately in the accepted set: it is where BACS and Cheque leave a
legitimate order awaiting funds, and nobody tests stolen cards by promising a bank
transfer. An unrecognised status degrades to "still an attempt" rather than
silently closing it.

Two adjacent defects in the same path, both of which also left rows open:

- `track_payment()` and `track_failure()` keyed their UPDATE on the CURRENT
  request's IP. Payment resolution usually arrives on an asynchronous gateway
  callback where that IP is the gateway's or absent, so the UPDATE matched
  nothing. Both now key on order_id alone, which already identifies the row.
- `get_recent_attempts()`'s exclusion is now NULL-safe. In MySQL
  `NULL <> 'success'` is NULL rather than TRUE, so a status-less row was silently
  dropped from the result instead of counting.

The new test caught a real bug in the first version of this fix:
`ltrim($status, 'wc-')` takes a character LIST, not a prefix, so it ate the
leading "c" of both "completed" and "cancelled" and answered wrongly for each.
Replaced with an explicit prefix strip.

80/80 tests (5 new), no new phpcs errors, phpstan unchanged.
@cport1
cport1 merged commit ef577e6 into main Jul 30, 2026
@cport1 cport1 mentioned this pull request Jul 30, 2026
cport1 added a commit that referenced this pull request Jul 30, 2026
Version bump plus changelog/readme for the four WooCommerce and reporting fixes
merged as #65 and #66 (issues #60, #61, #62, #63).

The 2.3.2 changelog carried a stated limitation — that the card-testing fix did
not apply to Cash on Delivery, Bank Transfer or Cheque stores. That limitation is
now resolved, so the entry says so rather than leaving the caveat standing.
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.

The card-testing success filter excludes nothing on COD/BACS/Cheque stores, because those gateways never call payment_complete()

1 participant