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>
Implements #4: students enrol in a group_class offering via the same
registration gate as private lessons (intake questions + booking-scoped
policy acceptance). Enrolment is capacity-enforced and prevents duplicates.
- Schema: us_group_enrollments table.
- Enrollment value object + EnrollmentRepository (countActiveForOffering,
hasActiveEnrollment, per-student/instructor/all-active queries, status).
- EnrollmentEndpoint: GET /enrollments (scoped) and POST /enrollments
(validates group_class, capacity, no-duplicate; reuses RegistrationGate;
records answers/acceptances type enrollment).
- GroupClassController + admin page (view_all_lessons): all active enrolments.
- Front-end: [us_group_classes] shortcode (GroupClassPage) + group-classes.js
enrol flow (list classes -> questions + policies -> POST /enrollments).
- Wiring in Plugin, RestRegistrar, AdminMenu, ShortcodeRegistrar.
Payment is the deferred seam (#7): enrolment lands active, payment_id null.
JS left untested for parity with the repo's no-build vanilla-JS posture.
Tests: tests/Unit/GroupClass/ (Enrollment, EnrollmentRepository).
composer test (121), cs, and PHPStan level 6 all pass.
Refs #4
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- RegistrationPage::maybeRedirectToRegistrationPage() (hooked on
template_redirect): any front-end request carrying a us_invite token is
redirected to the configured registration page (token preserved), unless
already there. Covers links shared before a page was selected; no-op when
no page is set.
- Invites button text: "Send Invite" -> "Generate Invitation Link".
- Doc updated.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Implements #16: invite-only student self-registration through a front-end
page, accepting signup-scoped policies at account creation.
Policy domain:
- us_policies.acceptance_scope (signup/booking/both); Policy::appliesTo();
PolicyRepository::findForScope(); scope threaded through PolicyService,
the REST create, the admin controller, and the Policies form.
- PolicyAcceptance::REG_ACCOUNT (registration_id = the new user's ID).
Auth:
- Invite value object + InviteRepository; us_invites table.
- RegistrationController + Invites admin page (manage_students): invite an
email, share the registration link, revoke.
- RegistrationPage ([us_student_register] shortcode): validates the invite
token, collects name/password, renders signup-scoped published policies
with required acceptance, creates the us_student user, records account-type
acceptances, marks the invite accepted, and logs the user in.
- RoleManager: manage_students cap added to STUDIO_ADMIN_CAPS.
Invite-only is implemented; the us_registration_mode self_approval path is a
documented future seam.
Docs: docs/features/account-registration.md; policies.md updated.
Tests: tests/Unit/Auth/ (Invite, InviteRepository) plus Policy scope
updates. composer test (104), cs, and PHPStan level 6 all pass.
Refs #16
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
All classes are now organised by domain (Availability, Booking, Auth).
Each domain package contains its value object, repository, admin controller,
REST endpoint, and any shortcode pages under a matching sub-namespace.
Cross-cutting wiring (Plugin, AdminMenu, RestRegistrar, ShortcodeRegistrar,
Schema) lives at src/ root. Tests mirror the domain structure.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>