2 Commits
Author SHA1 Message Date
thatguygriffandClaude Opus 5 b772e1811e Let parents register once and book for their children
A parent registers once and manages lessons for one or more children, who
need no login of their own. A child is a real wp_users row with the student
role but no usable login — so student_id keeps meaning "a WordPress user"
on every table, and booking, credits, policies and enrolments work unchanged.
A us_guardians link table maps guardian to child.

The signup form gains a parent/guardian tick that reveals a block per child,
with the account-signup questions asked per child rather than per guardian
— they describe the student, not the account holder. Signup policies are
recorded once per child with the guardian as the acceptor, which is the
record that actually means something. A family that half-creates is rolled
back entirely rather than leaving a guardian who cannot re-register.

The booking and enrolment forms gain a "Who is this for?" picker listing
children first, so the default selection is never the parent — booking for
the wrong child is correctable, quietly billing a parent for their kid's
lesson is not. POST /bookings and POST /enrollments take an optional
student_id honoured only for that child's guardian; anything else is a 403.
That check is the authorisation boundary of the feature.

Payments and credits gain a payer: the charge names the child it was for and
the guardian who owes it, so per-child reporting is unchanged while notices,
receipts and the payment step reach the parent. Credit is held by the payer,
so one child's cancellation can settle a sibling's charge, and the daily
billing scan sends a guardian one notice covering every child.

Closes #132

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-07-29 16:07:52 -03:00
thatguygriffandClaude Opus 4.8 925a4b79ba Add live Stripe card charges (PaymentIntent + Elements + webhook)
CI / No Debug Code (pull_request) Successful in 40s
CI / Tests (PHP 8.2) (pull_request) Successful in 48s
CI / Coding Standards (pull_request) Successful in 1m0s
CI / PHPStan (pull_request) Successful in 1m13s
CI / Tests (PHP 8.1) (pull_request) Successful in 2m9s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m8s
CI / Build Plugin Zip (pull_request) Has been skipped
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 <[email protected]>
2026-06-08 15:51:37 -03:00