Adds two new offering billing modes — weekly and monthly — alongside the existing one-time and full-term, for both private lessons and group classes.
Weekly — a pending payment for one lesson's fee is generated 24 hours before each lesson.
Monthly — on the 1st of each month a single pending payment is generated for every lesson that falls in that month (4 lessons ⇒ 4 × fee).
Scheduled offerings are not charged at registration; the booking/enrolment succeeds with payment: null and payments are generated later by a daily WP-Cron scan.
What's included
Offering model/form — weekly/monthly modes, isScheduledBilling(), and the two new options on the admin offering form.
Deferred billing — BookingEndpoint/EnrollmentEndpoint skip the payment step for scheduled modes. A single lesson booked after its scheduled due date has passed (e.g. an add-on lesson in an already-billed month, or a weekly lesson booked within 24h) is charged at booking instead, so it's never billed late.
ScheduledBillingRunner — daily us_generate_due_payments cron scan generating due pending payments across four cases (private/group × weekly/monthly), reusing PaymentService (HST, method resolution, e-transfer freezing, comp). Deduped via us_lessons.payment_id (private) and us_payments.period_key (group). Self-healing — a missed day is picked up next run.
PaymentDueMailer — one consolidated, itemised email per student per scan (line per lesson/period + grand total), never one per lesson.
Notice batch — payments emailed together share a reference (us_payments.notice_batch), printed on the email; the admin Payments queue groups them with a combined lump-sum total so a single e-transfer reconciles to the exact pending payments (and bookings) it covers.
Cancellation-safe — scheduled payments (Payment::isScheduled()) are never auto-voided, so cancelling one lesson never voids a shared monthly charge, refunds, or rebills.
Schema — us_payments gains due_date, period_key, notice_batch; USC_VERSION bumped to 1.2.0; daily event scheduled in Installer and cleared on deactivation.
Docs — new docs/features/scheduled-billing.md, plus updates to offerings.md and payments.md.
Tests
composer test — 559 tests, 1623 assertions, all pass (28 new)
composer lint (PHPStan level 10) — clean
composer cs (PHPCS WordPress) — clean
## Summary
Adds two new offering billing modes — **weekly** and **monthly** — alongside the existing one-time and full-term, for both private lessons and group classes.
- **Weekly** — a pending payment for one lesson's fee is generated **24 hours before each lesson**.
- **Monthly** — on the **1st of each month** a single pending payment is generated for every lesson that falls in that month (4 lessons ⇒ 4 × fee).
Scheduled offerings are **not** charged at registration; the booking/enrolment succeeds with `payment: null` and payments are generated later by a daily WP-Cron scan.
## What's included
- **Offering model/form** — `weekly`/`monthly` modes, `isScheduledBilling()`, and the two new options on the admin offering form.
- **Deferred billing** — `BookingEndpoint`/`EnrollmentEndpoint` skip the payment step for scheduled modes. A single lesson booked **after** its scheduled due date has passed (e.g. an add-on lesson in an already-billed month, or a weekly lesson booked within 24h) is charged **at booking** instead, so it's never billed late.
- **`ScheduledBillingRunner`** — daily `us_generate_due_payments` cron scan generating due pending payments across four cases (private/group × weekly/monthly), reusing `PaymentService` (HST, method resolution, e-transfer freezing, comp). Deduped via `us_lessons.payment_id` (private) and `us_payments.period_key` (group). Self-healing — a missed day is picked up next run.
- **`PaymentDueMailer`** — one consolidated, itemised email per student per scan (line per lesson/period + grand total), never one per lesson.
- **Notice batch** — payments emailed together share a reference (`us_payments.notice_batch`), printed on the email; the admin **Payments** queue groups them with a combined lump-sum total so a single e-transfer reconciles to the exact pending payments (and bookings) it covers.
- **Cancellation-safe** — scheduled payments (`Payment::isScheduled()`) are never auto-voided, so cancelling one lesson never voids a shared monthly charge, refunds, or rebills.
- **Schema** — `us_payments` gains `due_date`, `period_key`, `notice_batch`; `USC_VERSION` bumped to **1.2.0**; daily event scheduled in `Installer` and cleared on deactivation.
- **Docs** — new `docs/features/scheduled-billing.md`, plus updates to `offerings.md` and `payments.md`.
## Tests
- `composer test` — 559 tests, 1623 assertions, all pass (28 new)
- `composer lint` (PHPStan level 10) — clean
- `composer cs` (PHPCS WordPress) — clean
Offerings can now bill weekly (a pending payment 24h before each lesson)
or monthly (one payment on the 1st for that month's lessons), alongside
one-time and full-term. Applies to both private lessons and group classes.
- Offering: new `weekly`/`monthly` billing modes + `isScheduledBilling()`
- Booking/enrolment defer payment for scheduled modes; a single lesson
booked after its due date has passed (e.g. an add-on in an already-billed
month) is charged at booking instead
- ScheduledBillingRunner: daily WP-Cron scan generates due payments across
four cases (private/group × weekly/monthly), deduped via lesson.payment_id
and payments.period_key
- PaymentDueMailer: one consolidated itemised email per student per scan
- Notice batch: payments emailed together share a reference; the admin
Payments queue groups them with a lump-sum total for e-transfer reconciliation
- Cancellation never voids a scheduled payment (Payment::isScheduled())
- Schema: us_payments gains due_date, period_key, notice_batch; USC_VERSION 1.2.0
composer test, composer lint, composer cs all pass.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
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
Adds two new offering billing modes — weekly and monthly — alongside the existing one-time and full-term, for both private lessons and group classes.
Scheduled offerings are not charged at registration; the booking/enrolment succeeds with
payment: nulland payments are generated later by a daily WP-Cron scan.What's included
weekly/monthlymodes,isScheduledBilling(), and the two new options on the admin offering form.BookingEndpoint/EnrollmentEndpointskip the payment step for scheduled modes. A single lesson booked after its scheduled due date has passed (e.g. an add-on lesson in an already-billed month, or a weekly lesson booked within 24h) is charged at booking instead, so it's never billed late.ScheduledBillingRunner— dailyus_generate_due_paymentscron scan generating due pending payments across four cases (private/group × weekly/monthly), reusingPaymentService(HST, method resolution, e-transfer freezing, comp). Deduped viaus_lessons.payment_id(private) andus_payments.period_key(group). Self-healing — a missed day is picked up next run.PaymentDueMailer— one consolidated, itemised email per student per scan (line per lesson/period + grand total), never one per lesson.us_payments.notice_batch), printed on the email; the admin Payments queue groups them with a combined lump-sum total so a single e-transfer reconciles to the exact pending payments (and bookings) it covers.Payment::isScheduled()) are never auto-voided, so cancelling one lesson never voids a shared monthly charge, refunds, or rebills.us_paymentsgainsdue_date,period_key,notice_batch;USC_VERSIONbumped to 1.2.0; daily event scheduled inInstallerand cleared on deactivation.docs/features/scheduled-billing.md, plus updates toofferings.mdandpayments.md.Tests
composer test— 559 tests, 1623 assertions, all pass (28 new)composer lint(PHPStan level 10) — cleancomposer cs(PHPCS WordPress) — clean