Commit Graph
13 Commits
Author SHA1 Message Date
Kydoimosandanthropic/claude-opus-4-8 a2c609803e Add editable, previewable payment-due email template
CI / Coding Standards (pull_request) Successful in 29s
CI / Tests (PHP 8.2) (pull_request) Successful in 31s
CI / Tests (PHP 8.3) (pull_request) Successful in 31s
CI / Tests (PHP 8.5) (pull_request) Successful in 31s
CI / No Debug Code (pull_request) Successful in 9s
CI / Tests (PHP 8.1) (pull_request) Successful in 44s
CI / Static Analysis (pull_request) Successful in 51s
CI / Build Plugin Zip (pull_request) Skipped
The "Payment due" notice the daily billing scan sends was fixed wording
baked into PaymentDueMailer. Studio admins can now view, edit and preview
its subject and body under Studio Settings -> Payment Due Email.

- PaymentDueEmailTemplate stores subject/body/item-line as us_payment_due_email_*
  options, each falling back to the built-in default when blank, and renders
  via {token} substitution.
- PaymentDueMailer now builds a token map from the same consolidated
  items/credit/e-transfer/reference data and renders the stored template; the
  default template reproduces the previous wording exactly.
- PaymentEmailController serves the view/edit/save/reset admin page
  (manage_billing, nonce-checked) with a server-rendered sample preview.
- PaymentEmailPreviewEndpoint (POST us-scheduler/v1/payment-email/preview,
  manage_billing) + payment-email-admin.js drive a live preview of unsaved edits.
- Submenu wired under Studio Settings; endpoint wired in RestRegistrar.
- Bump to 1.6.0 and add CHANGELOG entry. New options default gracefully, so
  existing sites need no migration and send the identical notice until edited.

Co-authored-by: anthropic/claude-opus-4-8
2026-09-17 17:33:23 -03:00
Kydoimosandanthropic/claude-opus-4-8 1a166eb4e3 Add changelog entries for the credit fix and student-page display
CI / Coding Standards (pull_request) Successful in 30s
CI / Tests (PHP 8.2) (pull_request) Successful in 33s
CI / Tests (PHP 8.5) (pull_request) Successful in 33s
CI / Static Analysis (pull_request) Successful in 38s
CI / No Debug Code (pull_request) Successful in 9s
CI / Tests (PHP 8.1) (pull_request) Successful in 42s
CI / Tests (PHP 8.3) (pull_request) Successful in 41s
CI / Build Plugin Zip (pull_request) Skipped
Recorded under the current 1.5.8 section; no version bump.

Co-authored-by: anthropic/claude-opus-4-8
2026-09-17 15:49:16 -03:00
Kydoimosandanthropic/claude-opus-4-8 362980d008 Apply available credit to any at-booking charge, not just scheduled
CI / Coding Standards (pull_request) Successful in 31s
CI / Tests (PHP 8.2) (pull_request) Successful in 30s
CI / Tests (PHP 8.3) (pull_request) Successful in 31s
CI / No Debug Code (pull_request) Successful in 5s
CI / Tests (PHP 8.1) (pull_request) Successful in 43s
CI / Tests (PHP 8.5) (pull_request) Successful in 42s
CI / Static Analysis (pull_request) Successful in 56s
CI / Build Plugin Zip (pull_request) Skipped
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
2026-09-17 15:46:11 -03:00
Kydoimosandanthropic/claude-opus-4-8 e3ab7973d6 Clarify credit-at-booking comment scopes to scheduled charges
CI / Coding Standards (pull_request) Successful in 30s
CI / Tests (PHP 8.2) (pull_request) Successful in 32s
CI / Tests (PHP 8.3) (pull_request) Successful in 37s
CI / Static Analysis (pull_request) Successful in 38s
CI / No Debug Code (pull_request) Successful in 9s
CI / Tests (PHP 8.1) (pull_request) Successful in 46s
CI / Tests (PHP 8.5) (pull_request) Successful in 45s
CI / Build Plugin Zip (pull_request) Skipped
Co-authored-by: anthropic/claude-opus-4-8
2026-09-17 15:44:41 -03:00
Kydoimosandanthropic/claude-opus-4-8 1758c253a1 Trim credit-at-booking comment to one line
CI / Coding Standards (pull_request) Successful in 23s
CI / No Debug Code (pull_request) Successful in 9s
CI / Tests (PHP 8.2) (pull_request) Successful in 33s
CI / Tests (PHP 8.5) (pull_request) Successful in 34s
CI / Static Analysis (pull_request) Successful in 39s
CI / Tests (PHP 8.3) (pull_request) Successful in 38s
CI / Tests (PHP 8.1) (pull_request) Successful in 46s
CI / Build Plugin Zip (pull_request) Skipped
Co-authored-by: anthropic/claude-opus-4-8
2026-09-17 15:43:57 -03:00
Kydoimosandanthropic/claude-opus-4-8 67017409d7 Drop stale comment about not applying credit at booking
CI / Coding Standards (pull_request) Successful in 29s
CI / Tests (PHP 8.1) (pull_request) Successful in 36s
CI / No Debug Code (pull_request) Successful in 11s
CI / Tests (PHP 8.5) (pull_request) Successful in 36s
CI / Static Analysis (pull_request) Successful in 41s
CI / Tests (PHP 8.2) (pull_request) Successful in 43s
CI / Tests (PHP 8.3) (pull_request) Successful in 43s
CI / Build Plugin Zip (pull_request) Skipped
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
2026-09-17 15:42:38 -03:00
Kydoimosandanthropic/claude-opus-4-8 e7de627752 Apply account credit on same-month rebook, surface credit on student page
CI / Coding Standards (pull_request) Successful in 27s
CI / Tests (PHP 8.2) (pull_request) Successful in 28s
CI / No Debug Code (pull_request) Successful in 9s
CI / Tests (PHP 8.5) (pull_request) Successful in 35s
CI / Tests (PHP 8.1) (pull_request) Successful in 37s
CI / Tests (PHP 8.3) (pull_request) Successful in 43s
CI / Static Analysis (pull_request) Successful in 51s
CI / Build Plugin Zip (pull_request) Skipped
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
2026-09-17 15:39:53 -03:00
Kydoimosandanthropic/claude-opus-4-8 acda3cda0f Reconcile up-front charges when a class switches to monthly billing
CI / Coding Standards (pull_request) Successful in 25s
CI / No Debug Code (pull_request) Successful in 3s
CI / Tests (PHP 8.1) (pull_request) Successful in 36s
CI / Tests (PHP 8.2) (pull_request) Successful in 39s
CI / Tests (PHP 8.3) (pull_request) Successful in 51s
CI / Tests (PHP 8.5) (pull_request) Successful in 59s
CI / Static Analysis (pull_request) Successful in 1m3s
CI / Build Plugin Zip (pull_request) Skipped
A student who enrols while a group class is pay-now is charged once at
enrolment, and that charge carries no period_key. When the class is
later switched to monthly, the daily scan — which dedups scheduled
charges by period_key — does not see the up-front charge and bills the
enrolment again for the current month, double-charging students who
had already paid. The differing payer between the two rows (student vs
guardian) was a side effect of guardian links created between the two
charge dates, not the cause.

Switching a group class into monthly now adopts each active enrolment's
up-front charge into the current month (stamping period_key and
due_date) so the scan treats that month as billed and charges from the
next month on. Enrolments with no up-front charge, or already billed
for the month, are left alone; weekly and non-group offerings are not
touched. Wired into both offering-update paths (admin form and REST).

Co-authored-by: anthropic/claude-opus-4-8
2026-09-17 15:07:22 -03:00
Kydoimosandanthropic/claude-opus-4-8 e389e40843 Send each scheduled payment's due notice exactly once
CI / Coding Standards (pull_request) Successful in 28s
CI / Tests (PHP 8.1) (pull_request) Successful in 35s
CI / No Debug Code (pull_request) Successful in 9s
CI / Tests (PHP 8.3) (pull_request) Successful in 44s
CI / Tests (PHP 8.2) (pull_request) Successful in 47s
CI / Tests (PHP 8.5) (pull_request) Successful in 48s
CI / Static Analysis (pull_request) Successful in 52s
CI / Build Plugin Zip (pull_request) Skipped
The daily billing scan runs on request via WP-Cron and can overlap
itself under concurrent traffic. Each run emailed the payments it
created with no record that a notice had gone out, so two overlapping
runs could send a payer two identical "Payment due" emails for one
charge — read by families as being billed twice, though only one row
exists.

Stamp us_payments.notice_sent_at atomically before emailing: the scan
now claims each payment with a conditional UPDATE ... WHERE
notice_sent_at IS NULL and only notices, credits and batches the rows
it won. A competing run finds them claimed and stays quiet, so exactly
one notice is sent regardless of how the scan is triggered. A one-time
backfill stamps existing scheduled rows on upgrade so already-noticed
charges are not re-emailed.

Co-authored-by: anthropic/claude-opus-4-8
2026-09-17 13:19:25 -03:00
Kydoimosandopencode/muse-spark-1.3-contributor-free 63ee248695 Add AGENTS.md, point CLAUDE.md files at it
CI / Coding Standards (pull_request) Successful in 14s
CI / No Debug Code (pull_request) Successful in 3s
CI / Tests (PHP 8.1) (pull_request) Successful in 34s
CI / Tests (PHP 8.3) (pull_request) Successful in 43s
CI / Tests (PHP 8.5) (pull_request) Successful in 45s
CI / Tests (PHP 8.2) (pull_request) Successful in 1m0s
CI / Static Analysis (pull_request) Successful in 1m2s
CI / Build Plugin Zip (pull_request) Skipped
Co-authored-by: opencode/muse-spark-1.3-contributor-free
2026-09-16 12:21:55 -03:00
KydoimosandClaude Opus 5 c4b2b5ccff Sign the automated version bump commit in CI
CI / Coding Standards (pull_request) Successful in 23s
CI / Tests (PHP 8.5) (pull_request) Successful in 27s
CI / Tests (PHP 8.2) (pull_request) Successful in 35s
CI / Static Analysis (pull_request) Successful in 39s
CI / Tests (PHP 8.3) (pull_request) Successful in 40s
CI / Tests (PHP 8.1) (pull_request) Successful in 49s
CI / No Debug Code (pull_request) Successful in 35s
CI / Build Plugin Zip (pull_request) Skipped
main now requires signed commits, and Gitea refuses to merge a pull request
carrying an unsigned one, so the post-release bump PR could not be merged
without disabling the protection first.

The runner now signs that commit with a dedicated release-bot SSH key held as
an organisation Actions secret, trusted by the instance through
[repository.signing] TRUSTED_SSH_KEYS. The key Gitea signs merge commits with
lives on the server and no runner can reach it.

The step fails fast when the secret is missing, unparseable or passphrase
protected, and the commit is re-read before pushing so an unsigned one is
caught here rather than days later at merge time.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-05 20:25:58 -03:00
KydoimosandClaude Opus 5 ae1a62883e Fix the CI failures in the uninstaller work
CI / Coding Standards (pull_request) Successful in 28s
CI / No Debug Code (pull_request) Successful in 3s
CI / Tests (PHP 8.1) (pull_request) Successful in 38s
CI / Tests (PHP 8.3) (pull_request) Successful in 40s
CI / Tests (PHP 8.2) (pull_request) Successful in 49s
CI / Static Analysis (pull_request) Successful in 1m0s
CI / Tests (PHP 8.5) (pull_request) Successful in 57s
CI / Build Plugin Zip (pull_request) Skipped
Both were mine, and both were in code the earlier commit could not run.

The four test failures shared one cause: UninstallerTest stubbed get_option
with an arrow function, which captures by value, so every read answered from
a snapshot of the options taken at setUp — before the test set any and before
the run wrote any. Every assertion that depended on reading back what had
just been written therefore saw an empty store. The file's other stubs
already use by-reference closures; this one now does too.

The phpcs error is WordPress.DB.PreparedSQL.NotPrepared on the table drop.
The sniff cannot follow $sql across the null guard that PHPStan requires
(prepare() is nullable), and unlike the repositories — which call through a
typed $this->db property the sniff does not track at all — the uninstaller
calls the global $wpdb, so the sniff sees it. Silenced explicitly, with the
reason.

composer test (996 tests, 2871 assertions), composer lint and composer cs all
pass locally on PHP 8.4.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-05 11:55:53 -03:00
KydoimosandClaude Opus 5 1847159e31 Fix five findings from a security assessment of the plugin
The assessment looked for three things: whether students can reach each
other's bookings, whether payment settings can be dodged, and whether the
plugin opens a way into the rest of the install. The student-isolation and
payment paths held up. These are what did not.

- The front-end login form told WordPress not to work out whether the site
  was secure, so on HTTPS every student's session cookie was issued without
  the Secure flag. wp_signon() only derives it from is_ssl() when the second
  argument is left at its default; an explicit false reads like "no
  preference" and is not.

- The update check took whatever download URL the release API returned and
  handed it to core, which unpacks it over the installed plugin. The package
  must now be https on git.unsupervised.ca exactly, compared on the parsed
  host so a lookalike name cannot pass.

- Uninstalling dropped 2 of 14 tables and left the Stripe secret and webhook
  signing key in wp_options. Removal is now a choice made in advance on
  Access -> Plugin removal: records are kept unless the owner opts in (with a
  typed confirmation), while credentials and the borrowed core registration
  settings go every time.

- Open registration switches on the site-wide users_can_register and makes
  Student the default role, arming any other signup form on the site to mint
  students who could book and be billed immediately. The pending state is now
  decided once, on user_register, rather than by whichever form created the
  account.

- Cancel and withdraw answered "not yours" differently from "does not exist",
  which let a signed-in student enumerate the studio's bookings. Both now
  give the same 404.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-05 11:55:53 -03:00