Problem/Motivation
CancelLinkBuilder describes itself as "the one place that mints the signed cancel URL, so every surface that offers cancellation (the confirmation screen, the confirmation email, an account view) asks the same question and gets the same link". Only the mail ever asks.
A booker reading their confirmation has no way to cancel, although the cancellation window is open beside them: the workflow forks into n_cancel_window and n_await_guarantee together, and the booking_cancel interaction only renders once the booker arrives from the mailed link.
Proposed resolution
Add [yoyaku-order:cancel-url], minted by the same builder, so a message can carry the link from config the way [yoyaku-order:bookings] already carries the booking list. No new plugin and no new render path, and a page and a mail cannot drift apart about whether cancellation is open or about where the link goes.
The token clears where cancellation is closed, so a surface that prints it never shows a link that would refuse on arrival. The link stops working at the cancellation deadline, so the replacement carries the time left as a max age, and is not cacheable at all where no deadline is known.
Why the mail's own mechanism does not cover this. The confirmation mail carries the link as [orchestra-notification:link], the notification's generic link, and OrderNotifier picks the template from whether that link exists: a booking that cannot be canceled gets the plain confirmed template, which has no cancel section at all. That is how the mail avoids a dead button, and it works only because a notifier can choose between templates. A surface that renders inline, such as the confirmation screen or an account view, has no such choice. That is what this token is for: it renders nothing at all when the window is closed.
Remaining tasks
None. Placing the token in the shipped workflow message is left to whoever wants it there.
User interface changes
None by itself. It makes the link available to any surface that wants it.
API changes
Two new tokens. [yoyaku-order:cancel-url] is the bare signed address, for a template that builds its own link. [yoyaku-order:cancel-link] is the same address as a ready anchor, for a message that prints what it is handed: an empty answer renders nothing at all, which is what a surface that cannot switch templates needs. Both are empty when the booking cannot be canceled.
OrderTokenHooks takes CancelLinkBuilder and the time service, and the replacement carries the time left before the cancellation deadline as a max age.
Data model changes
None.
AI-Generated: Yes (Claude Code was used to help draft this issue summary and to write the code and tests on the merge request. I reviewed and ran the work myself before posting it.)
Issue fork yoyaku-3620301
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #3
mably commentedComment #4
mably commentedComment #5
mably commentedComment #7
mably commented