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>