Cancellation and refund policy¶
Whether a booker may cancel their own booking is a core concern; whether a
cancellation is refunded is a payment concern, owned by yoyaku_payment.
Both are policy on the resource type with a per-resource override, and
both are off by default: a resource is non-cancellable and non-refundable
until it is explicitly allowed. This keeps self-service cancellation opt-in, so
it is never offered where it should not be.
The two gates¶
| Gate | Lives in | Window | Effect when on |
|---|---|---|---|
allow_self_cancellation |
core | cancellation_deadline (+ unit) |
The booker can cancel online up to the deadline; later, only an operator can. |
allow_refund |
yoyaku_payment |
refund_deadline (+ unit) |
A cancellation before the deadline is refunded; a later one is allowed but not refunded. |
A deadline of zero means "up to the slot start". The two gates are
independent: a resource can refund operator-initiated cancellations while
forbidding self-service cancellation, and vice versa. The refund gate only
exists when yoyaku_payment is installed; core never knows about refunds.
Answered on the resource, inherited from a level¶
The policy lives on the resource. A resource holding inherit on one of
these fields, or holding nothing, is answered by the level that rules it
through its field rules: its resource type, then its tenant,
then the site. Where no level names a value either, canceling online is off,
which is the safe way round.
Across an order that spans several resources the strictest wins: one non-cancellable resource makes the whole order non-cancellable, and the longest deadline applies.
- Cancellation resolves through
CancellationPolicyResolver(core), readingallow_self_cancellationandcancellation_deadlineoff the resource throughFieldRuleResolver::getEffectiveValue(). - Refund resolves through
RefundPolicyResolver(yoyaku_payment), reading its ownallow_refundandrefund_deadlineresource fields the same way. Both aggregate overBookingScheduleResolver::getConsumingResources(), the public building section a submodule uses to walk an order's resources.
How canceling works, end to end¶
Cancellation is a self-contained core capability that does not depend on the workflow engine:
- The confirmation email (and any other surface) carries a signed link,
/booking/cancel/{token}. The token is minted byCancelToken: an HMAC over the order uuid and an expiry set to the cancellation deadline, keyed by the site secret. It can be issued the moment the order is confirmed. CancelControllerverifies the token, then re-checks the policy (still allowed, still before the deadline). Visiting the link only ever renders a confirm screen; nothing is canceled on a bare GET, so a link prefetch cannot cancel a booking.- It dispatches a claimable
BookingCancellationRequestedEvent. When the order is run by a workflow,yoyaku_orchestra_order'sCancelRequestSubscriberclaims it and redirects the booker into the existingbooking_cancelinteraction, so the workflow drives the cancellation (its join, the operator refund review, and the notifications all run unchanged). When nothing claims it (no workflow), the controller shows its own confirm screen and cancels the order directly. - Orchestra also reacts to cancellations that start elsewhere (operator, cart,
expiry) by tearing down the running instance (
OrderCancellationSubscriber).
So the cancel endpoint works with or without Orchestra; with Orchestra the workflow stays in charge of the actual teardown and refund.
The confirmation email¶
The confirmation is a visible workflow step, notify:confirmed, next to
notify:canceled and notify:rejected — not a side effect of the cancel
window. It fires once per confirmed order. yoyaku_order's OrderNotifier picks
the template by whether the order is cancellable:
- cancellable ->
yoyaku_booking_cancellable, which shows the cancel button and the signed link (and a free area for site wording such as "or call us"); - non-cancellable ->
yoyaku_booking_confirmed, the plain confirmation with no cancel section.
Both ship with yoyaku_order_easy_email.
One email either way, both editable and translatable, so a non-cancellable booking never shows a dead cancel button.
Configuring it¶
- Resource type: edit the type and open the Cancellation & refund
section (labeled Cancellation when
yoyaku_paymentis absent). Tick Allow self-service cancellation and, with payment installed, Refund cancellations, and set the deadlines. - Resource: edit the resource and open the same section. Leave the gates on Inherit from type to follow the type, or override per resource.