Skip payment step for unpriced bookings, confirm them immediately, show students their lessons #54

Merged
thatguygriff merged 1 commits from fix/unpriced-booking-flow into main 2026-07-05 20:06:46 +00:00
Owner

Fixes #53.

Booking a slot without a priced offering succeeded server-side but the front end unconditionally called POST /payments/intent, which 400ed ("Could not start payment for this registration"). The student saw an error while the backend held a claimed slot and a lesson stuck at pending — and had no way to see their booked session anywhere.

Changes

1. Payment step is skipped when nothing is owed

  • POST /bookings and POST /enrollments now include a payment field in the response: a {id, method, status} summary (new Payment::toSummaryArray()), or null when no payment was created.
  • booking.js / group-classes.js only run usPayment.collect() when the response carries a payment.

2. Unpriced bookings are confirmed immediately

  • With no payment there is no later payment-finalization step to confirm the lesson, so BookingEndpoint::book() confirms the created lesson row(s) at creation. Priced bookings keep the existing pending → paid → confirmed flow, and comped payments (paid at creation) now report confirmed in the response.

3. Students can see their upcoming lessons

  • The booking page gets a "Your upcoming lessons" panel backed by GET /bookings, with a status badge (Pending payment / Confirmed). It refreshes on load and after a successful booking, and never blocks slot browsing if the fetch fails.
  • GET /bookings for students now returns upcoming non-cancelled lessons (new BookingRepository::findUpcomingForStudent(), mirroring the instructor query) instead of the full history, and every returned lesson includes the slot's start_dt/end_dt.

Tests

  • BookingEndpointTest: unpriced booking → confirmed + payment: null; priced → pending + summary; comped → confirmed; myLessons includes slot times.
  • New EnrollmentEndpointTest covering free vs priced enrolment responses.
  • BookingRepositoryTest::testFindUpcomingForStudentJoinsSlotAndExcludesCancelled, PaymentTest::testToSummaryArrayContainsOnlyClientFacingFields.

composer test (271 tests), composer lint, and composer cs all pass. Feature docs updated (lesson-booking.md, payments.md, group-classes.md), including the stale "payment seam deferred to #7" note.

🤖 Generated with Claude Code

Fixes #53. Booking a slot without a priced offering succeeded server-side but the front end unconditionally called `POST /payments/intent`, which 400ed ("Could not start payment for this registration"). The student saw an error while the backend held a claimed slot and a lesson stuck at `pending` — and had no way to see their booked session anywhere. ## Changes **1. Payment step is skipped when nothing is owed** - `POST /bookings` and `POST /enrollments` now include a `payment` field in the response: a `{id, method, status}` summary (new `Payment::toSummaryArray()`), or `null` when no payment was created. - `booking.js` / `group-classes.js` only run `usPayment.collect()` when the response carries a payment. **2. Unpriced bookings are confirmed immediately** - With no payment there is no later payment-finalization step to confirm the lesson, so `BookingEndpoint::book()` confirms the created lesson row(s) at creation. Priced bookings keep the existing pending → paid → confirmed flow, and comped payments (paid at creation) now report `confirmed` in the response. **3. Students can see their upcoming lessons** - The booking page gets a "Your upcoming lessons" panel backed by `GET /bookings`, with a status badge (Pending payment / Confirmed). It refreshes on load and after a successful booking, and never blocks slot browsing if the fetch fails. - `GET /bookings` for students now returns upcoming non-cancelled lessons (new `BookingRepository::findUpcomingForStudent()`, mirroring the instructor query) instead of the full history, and every returned lesson includes the slot's `start_dt`/`end_dt`. ## Tests - `BookingEndpointTest`: unpriced booking → confirmed + `payment: null`; priced → pending + summary; comped → confirmed; `myLessons` includes slot times. - New `EnrollmentEndpointTest` covering free vs priced enrolment responses. - `BookingRepositoryTest::testFindUpcomingForStudentJoinsSlotAndExcludesCancelled`, `PaymentTest::testToSummaryArrayContainsOnlyClientFacingFields`. `composer test` (271 tests), `composer lint`, and `composer cs` all pass. Feature docs updated (`lesson-booking.md`, `payments.md`, `group-classes.md`), including the stale "payment seam deferred to #7" note. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
thatguygriff added 1 commit 2026-07-05 20:03:13 +00:00
Skip payment step for unpriced bookings, confirm them immediately, show students their lessons
CI / No Debug Code (pull_request) Successful in 2s
CI / Tests (PHP 8.2) (pull_request) Successful in 53s
CI / PHPStan (pull_request) Successful in 2m46s
CI / Tests (PHP 8.1) (pull_request) Successful in 42s
CI / Coding Standards (pull_request) Successful in 47s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m35s
CI / Build Plugin Zip (pull_request) Has been skipped
5888032ed7
Booking a slot with no priced offering created the lesson but no payment,
yet the front end still called POST /payments/intent, which 400ed with
"Could not start payment for this registration" — the student saw an error
while the backend held a claimed slot and a lesson stuck at pending.

- POST /bookings and POST /enrollments now return a `payment` summary
  ({id, method, status}) or null when nothing is owed; the JS only runs
  the payment step when a payment exists.
- Bookings with nothing owed are confirmed at creation — there is no
  payment step that would ever confirm them later.
- The booking page now shows the student's upcoming lessons (GET /bookings,
  now scoped to upcoming non-cancelled lessons with slot start/end times)
  with a pending-payment/confirmed status badge.

Fixes #53

Co-Authored-By: Claude Fable 5 <[email protected]>
thatguygriff merged commit 9dd3c39ddd into main 2026-07-05 20:06:46 +00:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Unsupervised/unsupervised-scheduler#54