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
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
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]>
This commit is contained in:
+47
-5
@@ -5,9 +5,10 @@
|
||||
const app = document.getElementById('us-booking-app');
|
||||
if (!app) return;
|
||||
|
||||
const slotList = document.getElementById('us-slot-list');
|
||||
const confirm = document.getElementById('us-booking-confirmation');
|
||||
const errorBox = document.getElementById('us-booking-error');
|
||||
const slotList = document.getElementById('us-slot-list');
|
||||
const myLessons = document.getElementById('us-my-lessons');
|
||||
const confirm = document.getElementById('us-booking-confirmation');
|
||||
const errorBox = document.getElementById('us-booking-error');
|
||||
const { restUrl, nonce } = usScheduler;
|
||||
|
||||
function apiFetch(path, options = {}) {
|
||||
@@ -274,11 +275,51 @@
|
||||
accepted_policy_version_ids: accepted,
|
||||
}),
|
||||
})
|
||||
.then((res) => window.usPayment.collect('lesson', (res.ids || [])[0], slotList))
|
||||
.then((result) => showConfirmation(window.usPayment.message(result)))
|
||||
// A booking with nothing owed has no payment, so there is no payment
|
||||
// step to run — the booking is already confirmed server-side.
|
||||
.then((res) => (res.payment
|
||||
? window.usPayment.collect('lesson', (res.ids || [])[0], slotList)
|
||||
: null))
|
||||
.then((result) => {
|
||||
loadMyLessons();
|
||||
showConfirmation(window.usPayment.message(result));
|
||||
})
|
||||
.catch((err) => showError(err.message));
|
||||
}
|
||||
|
||||
function lessonStatusLabel(status) {
|
||||
if (status === 'pending') return 'Pending payment';
|
||||
if (status === 'confirmed') return 'Confirmed';
|
||||
return status.charAt(0).toUpperCase() + status.slice(1);
|
||||
}
|
||||
|
||||
function renderMyLessons(lessons) {
|
||||
const upcoming = lessons.filter((l) => l.start_dt);
|
||||
if (!upcoming.length) {
|
||||
myLessons.innerHTML = '';
|
||||
return;
|
||||
}
|
||||
|
||||
myLessons.innerHTML = `
|
||||
<div class="us-my-lessons">
|
||||
<h3>Your upcoming lessons</h3>
|
||||
${upcoming.map((l) => `
|
||||
<div class="us-my-lesson">
|
||||
<span>${escHtml(dayLabel(dayKey(l.start_dt)))} · ${escHtml(timeOf(l.start_dt))}–${escHtml(timeOf(l.end_dt))}</span>
|
||||
<span class="us-lesson-status us-lesson-status-${escHtml(String(l.status))}">${escHtml(lessonStatusLabel(String(l.status)))}</span>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
function loadMyLessons() {
|
||||
if (!myLessons) return;
|
||||
// The lesson list is a bonus panel: never let it break slot browsing.
|
||||
apiFetch('bookings')
|
||||
.then(renderMyLessons)
|
||||
.catch(() => { myLessons.innerHTML = ''; });
|
||||
}
|
||||
|
||||
function showConfirmation(message) {
|
||||
confirm.textContent = message;
|
||||
slotList.style.display = 'none';
|
||||
@@ -289,6 +330,7 @@
|
||||
clearError();
|
||||
slotList.style.display = 'block';
|
||||
confirm.style.display = 'none';
|
||||
loadMyLessons();
|
||||
apiFetch('availability')
|
||||
.then((slots) => {
|
||||
allSlots = slots;
|
||||
|
||||
@@ -142,7 +142,11 @@
|
||||
accepted_policy_version_ids: accepted,
|
||||
}),
|
||||
})
|
||||
.then((res) => window.usPayment.collect('enrollment', res.id, list))
|
||||
// An enrolment with nothing owed has no payment, so there is no
|
||||
// payment step to run.
|
||||
.then((res) => (res.payment
|
||||
? window.usPayment.collect('enrollment', res.id, list)
|
||||
: null))
|
||||
.then((result) => showConfirmation(window.usPayment.message(result)))
|
||||
.catch((err) => showError(err.message));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user