- Bump phpstan/phpstan ^2.0 and szepeviktor/phpstan-wordpress ^2.0
- Move the analysis level into phpstan.neon (single source) and raise it to 10
- Add Val, a runtime coercion helper that narrows untyped WordPress boundary
values (wpdb rows, REST params, superglobals, options) with explicit checks
instead of blind casts, plus unit tests
- Type value-object fromRow() params as stdClass (what wpdb returns) and map
columns through Val so unexpected shapes degrade safely
- Use %i identifier placeholders for table names in all wpdb::prepare() calls
so every query string is a literal and identifiers are escaped by WordPress;
raises the minimum WordPress version to 6.2 where %i was introduced
- Guard wpdb::prepare() null result before wpdb::query() in updateTax()
- Fix nullable get_permalink()/strtotime() handling, list types at REST and
capability call sites, dead null-coalescing on checked superglobals, and
narrow get_users() results before mapping
- Register Val method names with the ValidatedSanitizedInput sniff so it
validates the real sanitizer around each superglobal read
- Update repository unit tests for the %i placeholder arguments
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Completes the deferred half of payments: real credit-card processing on
top of the existing ledger/e-transfer/comp foundation.
- StripeGateway wraps stripe/stripe-php: creates idempotent PaymentIntents
(amount in cents, registration ids in metadata) and verifies webhook
signatures. Stripe calls sit behind protected seams for unit testing.
- PaymentService::createIntent resolves the client-side step for a new
registration (card → client secret; e-transfer → display data; comp →
none) with caller-ownership enforcement.
- PaymentService::handleWebhook finalises a payment exactly once on
payment_intent.succeeded (mark paid → confirm → receipt) and marks it
failed on payment_intent.payment_failed.
- PaymentEndpoint: POST /payments/intent (book_lesson) and public,
signature-verified POST /payments/webhook.
- PaymentRepository: setStripeIntentId / findByStripeIntentId.
- StudioSettings: us_stripe_webhook_secret option, with the webhook URL
and required events surfaced on the settings page.
- Front end: shared payment.js mounts Stripe Payment Elements and confirms
the card (or shows e-transfer instructions); Stripe.js enqueued only when
configured. Wired into booking and group-class flows.
Tests: new StripeGatewayTest; PaymentService card-intent + webhook cases;
repository coverage. composer test/lint/cs all green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>