# Order Configuration Hooks

https://dev.wisecp.com/es/order-configuration-hooks

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 PHP

```php
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 PHP

```php
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 PHP

```php
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 PHP

```php
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 PHP

```php
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 PHP

```php
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 PHP

```php
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 PHP

```php
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**.

## Related Articles

- [Cart Hooks](https://dev.wisecp.com/en/cart-hooks)
- [Order Status Hooks](https://dev.wisecp.com/en/order-status-hooks)
- [Service Status Hooks](https://dev.wisecp.com/en/service-status-hooks)
