Send each scheduled payment's due notice exactly once
CI / Coding Standards (pull_request) Successful in 28s
CI / Tests (PHP 8.1) (pull_request) Successful in 35s
CI / No Debug Code (pull_request) Successful in 9s
CI / Tests (PHP 8.3) (pull_request) Successful in 44s
CI / Tests (PHP 8.2) (pull_request) Successful in 47s
CI / Tests (PHP 8.5) (pull_request) Successful in 48s
CI / Static Analysis (pull_request) Successful in 52s
CI / Build Plugin Zip (pull_request) Skipped

The daily billing scan runs on request via WP-Cron and can overlap
itself under concurrent traffic. Each run emailed the payments it
created with no record that a notice had gone out, so two overlapping
runs could send a payer two identical "Payment due" emails for one
charge — read by families as being billed twice, though only one row
exists.

Stamp us_payments.notice_sent_at atomically before emailing: the scan
now claims each payment with a conditional UPDATE ... WHERE
notice_sent_at IS NULL and only notices, credits and batches the rows
it won. A competing run finds them claimed and stays quiet, so exactly
one notice is sent regardless of how the scan is triggered. A one-time
backfill stamps existing scheduled rows on upgrade so already-noticed
charges are not re-emailed.

Co-authored-by: anthropic/claude-opus-4-8
This commit is contained in:
2026-09-17 13:19:25 -03:00
co-authored by anthropic/claude-opus-4-8
parent 6c92fc35fd
commit e389e40843
11 changed files with 235 additions and 4 deletions
+10
View File
@@ -106,6 +106,16 @@ class PaymentService {
$this->payments->assignNoticeBatch( $ids, $batch );
}
/**
* Atomically claim a payment for its single due-payment notice, returning
* whether this call won the claim. The daily scan calls this before emailing
* so a payment is noticed exactly once even when WP-Cron overlaps. Delegates
* to the ledger.
*/
public function markNoticed( int $paymentId ): bool {
return $this->payments->markNoticed( $paymentId );
}
/**
* Studio-admin confirmation that a pending payment (e-transfer) was received.
* Marks it paid, confirms the registration, and emails the receipt.