Add weekly and monthly scheduled billing for offerings #105

Merged
thatguygriff merged 1 commits from feature/weekly-monthly-billing into main 2026-07-24 15:39:00 +00:00
Owner

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/formweekly/monthly modes, isScheduledBilling(), and the two new options on the admin offering form.
  • Deferred billingBookingEndpoint/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.
  • Schemaus_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
thatguygriff added 1 commit 2026-07-24 15:07:04 +00:00
Add weekly and monthly scheduled billing for offerings
CI / Tests (PHP 8.2) (pull_request) Successful in 39s
CI / Tests (PHP 8.1) (pull_request) Successful in 1m12s
CI / No Debug Code (pull_request) Successful in 3s
CI / PHPStan (pull_request) Successful in 2m52s
CI / Coding Standards (pull_request) Successful in 2m54s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m39s
CI / Build Plugin Zip (pull_request) Skipped
4328e8fb5f
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]>
thatguygriff merged commit d1dd30dc60 into main 2026-07-24 15:39:00 +00:00
thatguygriff deleted branch feature/weekly-monthly-billing 2026-07-24 15:39:00 +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#105