Order Configuration Hooks

2 vues Markdown

The eight hooks on the screens where a customer makes choices while ordering: configuring, adding a domain, adding an add-on, and provisioning.

Overview

The configuration screen is where a customer shapes a product to suit them: the domain choice, add-ons, the term, custom fields. The hooks here touch both the screen and whether a choice is allowed.

The last hook differs: it runs after payment, immediately before the services are built. It is the final stop between an order line and a service.

Reference

Stopping a configuration

gateorder.item_configure
ClientOrder the choices are gathered

Runs before the configured product is written into the cart, with the choices already gathered.

Parameters 3
$productarrayThe product record: id, type, name, add-ons, requirements, subdomains, options.
$cyclestringThe chosen billing cycle, already validated.
$optionsarrayThe gathered configuration, about to become the cart line's options: the domain choice (none, owned, register, transfer, subdomain), the add-ons, the custom fields. Every choice the customer made is here.
Return 1
stringA non-empty string stops the action; the text is shown to the customer as the error.
Listener
Hook::add('gate:order.item_configure', 10, function ($product, $cycle, $options) {
    // Every choice sits in $options: check your business rule here.
    if (($options['domain']['option'] ?? '') === 'owned'
        && !Acme::domainReachable($options['domain']['name'] ?? ''))
        return 'The domain you entered cannot be reached.';

    return null;
});

Changing the configuration screen

filterorder.configure_data
ClientOrder the whole page

Runs after the configuration page's data was built.

Parameters 2
$dataarrayrefThe page's whole template data: the product, the prices (raw amounts by cycle), the add-ons, the domain options. The prices arrive unformatted and are formatted on screen.
$ctxarrayThe context: the raw product record, its type, its id and the edit mark. A filled edit means the customer is editing a cart line rather than adding a new product.
Return 1
voidThe value changes by reference; the return is not read.
Listener
Hook::add('filter:order.configure_data', 10, function (&$data, $ctx) {
    // A filled edit means they are editing a cart line, not adding a product.
    if (!empty($ctx['edit'])) return;

    $data['acme_hint'] = Acme::upsellHint((int) ($ctx['id'] ?? 0));
});

Changing the add-on screen

filterorder.configure_addon_data
ClientOrder the context is a copy

Runs after the add-on purchase screen's data was built.

Parameters 2
$dataarrayrefThe page's whole template data. This is the one to change.
$ctxarrayrefThe context: the raw add-on record, the service list and the options. The lists are copies: passed by reference and yet a change here never reaches the screen.
Return 1
voidThe value changes by reference; the return is not read.
Listener
Hook::add('filter:order.configure_addon_data', 10, function (&$data, &$ctx) {
    // The lists inside $ctx are COPIES: change $data instead.
    $data['services'] = Acme::filterServices($data['services'] ?? []);
});

Stopping a domain going in the cart

gateorder.domain_add
ClientOrder the extension is validated

Runs before a domain is written into the cart.

Parameters 2
$tldRowarrayThe extension record: id, name, status, privacy, DNS management, forwarding add-ons. Already confirmed to be on sale.
$itemarrayThe line about to be written: the action (register or transfer), the full name, its parts, the years. The years is always 1 here; the term changes during configuration.
Return 1
stringA non-empty string stops the action; the text is shown to the customer as the error.
Listener
Hook::add('gate:order.domain_add', 10, function ($tldRow, $item) {
    // An extra check on transfers; none needed on a registration.
    if (($item['action'] ?? '') === 'transfer' && Acme::recentlyRegistered($item['domain'] ?? ''))
        return 'A newly registered domain cannot transfer for 60 days.';

    return null;
});

Stopping an add-on going in the cart

gateorder.service_addon_add
ClientOrder ownership is validated

Runs before an add-on is attached to a service, with ownership already confirmed.

Parameters 3
$servicearrayThe service the add-on attaches to: id, owner, product, type, status, term, due date.
$addonarrayThe add-on definition: id, name, type (select, radio, checkbox, quantity), status, properties.
$itemarrayThe line about to be written: the service, the add-on, the option, the quantity and upgrade_of. Above zero, upgrade_of marks an upgrade of an existing add-on rather than a new purchase.
Return 1
stringA non-empty string stops the action; the text is shown to the customer as the error.
Listener
Hook::add('gate:order.service_addon_add', 10, function ($service, $addon, $item) {
    // Above zero, upgrade_of marks an UPGRADE, not a new purchase.
    if ((int) ($item['upgrade_of'] ?? 0) > 0) return null;

    if (($service['status'] ?? '') !== 'active')
        return 'An add-on wants a live service to attach to.';

    return null;
});

Changing the services about to be built

filterorder.services_items
Orders::buildServices() the last stop before provisioning

Runs after payment, immediately before services are built from the order lines.

Parameters 2
$ctxarrayrefThe provisioning context: the order, the invoice, the owner, the currency, the status, the payment method, the languages, the time.
$itemsarrayrefThe product and domain lines about to be built. Dropping one here means that service is never built — even though the customer paid for it.
Return 1
voidThe value changes by reference; the return is not read.
Listener
Hook::add('filter:order.services_items', 10, function (&$ctx, &$items) {
    // DROPPING a line builds nothing: where the customer paid, make it good by hand.
    foreach ($items as &$i)
        $i['options']['acme_batch'] = Acme::batchFor((int) ($ctx['order_id'] ?? 0));
});

Following an affiliate change

actionorder.affiliate_changed
AdminOrders zero means removed

Runs after an order's affiliate changed.

Parameters 2
$order_idintThe order id.
$affiliate_idintThe new affiliate. 0 means the affiliate was removed, not that a new one arrived.
Return 1
voidThe return is ignored.
Listener
Hook::add('action:order.affiliate_changed', 10, function ($order_id, $affiliate_id) {
    // 0 means the affiliate was REMOVED; the commission may want taking back.
    if ($affiliate_id === 0) Acme::revokeCommission((int) $order_id);
});

Following a service removed from an order

actionorder.service.deleted
AdminOrders the order stays

Runs after a service was removed from an order. The order record stays where it is.

Parameters 2
$order_idintThe order id.
$service_idintThe id of the removed service.
Return 1
voidThe return is ignored.
Listener
Hook::add('action:order.service.deleted', 10, function ($order_id, $service_id) {
    Audit::orderLineRemoved((int) $order_id, (int) $service_id);
});

Pitfalls

Dropping a line at provisioning refunds nothing

The service-building filter runs after payment completed. Drop a line and that service is never built, while the customer has paid and the invoice stands. To prevent something, do it at the cart or checkout gate.

On the add-on screen the context is a copy

The second parameter is passed by reference and the lists inside it are copies: a change there never reaches the screen and your listener looks like it did nothing. What you mean to change is in the first parameter.

Adding an add-on can be an upgrade

Where upgrade_of on the add-on line is above zero the customer is not buying something new but upgrading what they have. A rule saying "they already have this, block a second" also blocks the upgrade.

The configuration screen also opens in edit mode

A filled edit in the screen filter's context means the customer is editing a cart line rather than adding a product. Showing an upsell or a welcome note in edit mode asks again about a choice they already made.

Cet article vous a-t-il été utile ?

Merci pour votre retour !

Besoin d'aide supplémentaire ?

Notre équipe d'assistance est disponible 24h/24 pour tout ce que vous ne trouvez pas ci-dessus.