Bug fix — credits not applied when rebooking a cancelled lesson within the same month.
Feature — show a student's total account credit on their detail page when they hold a balance.
Bug: same-month rebook charged twice
LessonBooker::settle() charges a scheduled (monthly/weekly) lesson at booking time when it is added to a month whose billing date has already passed — exactly the case of rebooking a cancelled lesson within the same month. That charge bypasses the daily billing scan (ScheduledBillingRunner::sendNotices), which was the only place applyCredits() ran. So the cancellation credit sat unused while the family was charged full price for the replacement slot — billed twice for the same lesson.
Fix: after creating the charge-at-booking payment for a scheduled offering, settle() now applies the payer's account credit to it, mirroring the daily scan. A payment fully covered by credit is settled and its lesson confirmed. applyCredits is a no-op when the payer holds no balance, and the one-time pay-now flow is deliberately left unchanged. Added PaymentService::findPayment() so settle() can re-read the row after applyCredits writes to it (the Payment object is immutable).
Feature: total credit on the student detail page
Added a prominent banner at the top of the student detail page, shown only when the student/family has a positive credit balance. Uses the already-available $creditBalance/$creditCurrency (no controller change), and notes when the balance is held on a guardian's account. The full "Account credit" breakdown remains lower on the page.
Tests
Added testMonthlyRebookInBilledMonthAppliesAccountCredit proving credit is applied and the lesson confirms when fully covered.
Added default mock expectations so existing charge-at-booking tests keep passing.
composer test (1015 tests), composer lint (PHPStan level 10), and composer cs all pass. No schema change, so no version bump required.
## Summary
Two changes to the credit system:
1. **Bug fix — credits not applied when rebooking a cancelled lesson within the same month.**
2. **Feature — show a student's total account credit on their detail page when they hold a balance.**
## Bug: same-month rebook charged twice
`LessonBooker::settle()` charges a scheduled (monthly/weekly) lesson *at booking time* when it is added to a month whose billing date has already passed — exactly the case of rebooking a cancelled lesson within the same month. That charge bypasses the daily billing scan (`ScheduledBillingRunner::sendNotices`), which was the **only** place `applyCredits()` ran. So the cancellation credit sat unused while the family was charged full price for the replacement slot — billed twice for the same lesson.
**Fix:** after creating the charge-at-booking payment for a scheduled offering, `settle()` now applies the payer's account credit to it, mirroring the daily scan. A payment fully covered by credit is settled and its lesson confirmed. `applyCredits` is a no-op when the payer holds no balance, and the one-time pay-now flow is deliberately left unchanged. Added `PaymentService::findPayment()` so `settle()` can re-read the row after `applyCredits` writes to it (the `Payment` object is immutable).
## Feature: total credit on the student detail page
Added a prominent banner at the top of the student detail page, shown only when the student/family has a positive credit balance. Uses the already-available `$creditBalance`/`$creditCurrency` (no controller change), and notes when the balance is held on a guardian's account. The full "Account credit" breakdown remains lower on the page.
## Tests
- Added `testMonthlyRebookInBilledMonthAppliesAccountCredit` proving credit is applied and the lesson confirms when fully covered.
- Added default mock expectations so existing charge-at-booking tests keep passing.
`composer test` (1015 tests), `composer lint` (PHPStan level 10), and `composer cs` all pass. No schema change, so no version bump required.
A monthly/weekly lesson booked into a month whose billing date has
already passed is charged at booking time by LessonBooker::settle,
bypassing the daily scan where account credit is otherwise applied. So
rebooking a cancelled paid lesson within the same month charged the
family in full while their cancellation credit sat unused — billed twice
for the same slot.
Apply the payer's credit to that charge-at-booking payment for scheduled
offerings, mirroring the daily scan: a payment fully covered by credit
settles and confirms its lesson. Add PaymentService::findPayment so
settle can re-read the row after applyCredits writes to it.
Also surface the student's total account credit at the top of their
detail page when they hold a balance, so the studio sees it at a glance.
Co-authored-by: anthropic/claude-opus-4-8
This change applies credit at booking for scheduled rebooks, so the note
saying the pay-now flow deliberately does not was contradictory.
Co-authored-by: anthropic/claude-opus-4-8
A payer's account credit should offset any charge raised at booking, so
drop the scheduled-only guard. Covers the one-time pay-now flow too.
Co-authored-by: anthropic/claude-opus-4-8
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
Two changes to the credit system:
Bug: same-month rebook charged twice
LessonBooker::settle()charges a scheduled (monthly/weekly) lesson at booking time when it is added to a month whose billing date has already passed — exactly the case of rebooking a cancelled lesson within the same month. That charge bypasses the daily billing scan (ScheduledBillingRunner::sendNotices), which was the only placeapplyCredits()ran. So the cancellation credit sat unused while the family was charged full price for the replacement slot — billed twice for the same lesson.Fix: after creating the charge-at-booking payment for a scheduled offering,
settle()now applies the payer's account credit to it, mirroring the daily scan. A payment fully covered by credit is settled and its lesson confirmed.applyCreditsis a no-op when the payer holds no balance, and the one-time pay-now flow is deliberately left unchanged. AddedPaymentService::findPayment()sosettle()can re-read the row afterapplyCreditswrites to it (thePaymentobject is immutable).Feature: total credit on the student detail page
Added a prominent banner at the top of the student detail page, shown only when the student/family has a positive credit balance. Uses the already-available
$creditBalance/$creditCurrency(no controller change), and notes when the balance is held on a guardian's account. The full "Account credit" breakdown remains lower on the page.Tests
testMonthlyRebookInBilledMonthAppliesAccountCreditproving credit is applied and the lesson confirms when fully covered.composer test(1015 tests),composer lint(PHPStan level 10), andcomposer csall pass. No schema change, so no version bump required.New commits pushed, approval review dismissed automatically according to repository settings