# Cart Hooks

https://dev.wisecp.com/es/cart-hooks

Eight hooks from a customer filling a cart to the order being placed. The add gates, the pricing chain and the checkout step.

## Overview

Cart pricing is a **three-stage chain** and each stage has its own filter. The raw inputs come first, then the priced lines, then the summary. Knowing the order puts your rule on **the right link**.

Picking the wrong link fails quietly. Change a total on the item filter and the summary **works it out again**, writing over you.

## Reference

### Stopping something going in the cart

gateorder.cart_add

`Cart` the product is validated

Runs before the product is written into the cart, with the product and cycle **already validated**.

Parameters 3

$productarrayThe product record: id, type, name, stock, add-ons, options. Already confirmed to be on sale.

$cyclestringThe billing cycle chosen. Confirmed to be one the product actually offers.

$itemarrayThe line about to be written: kind, product id, cycle, quantity, the configured mark, the options.

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.cart_add', 10, function ($product, $cycle, $item) {
    // The core checks stock; add your own business rule.
    if (Acme::limitReached($product['id'] ?? 0))
        return 'The daily order limit for this product is spent.';

    return null;
});
```

### Changing the raw inputs

filterorder.cart_inputs

`Orders::buildCart()` the first link

Runs on the raw order inputs, before pricing starts.

Parameters 2

$inputsarrayrefThe raw inputs, each with its own group (domain, product, add-on). This is the **head** of the chain: adding a line here means the next two stages **price it** for you.

$ucidintThe currency the pricing runs in.

Return 1

voidThe value changes **by reference**; the return is not read.

Listener PHP

```php
Hook::add('filter:order.cart_inputs', 10, function (&$inputs, $ucid) {
    // The HEAD of the chain: a line added here gets priced by the core.
    if (Acme::freeSslCampaign())
        $inputs[] = ['group' => 'product', 'product_id' => Acme::SSL_ID, 'cycle' => 'annually'];
});
```

### Changing the priced lines

filterorder.cart_items

`Orders::buildCart()` the second link

Runs after the lines were priced, before the summary is built.

Parameters 4

$itemsarrayrefThe priced lines: type, product or extension, unit price, total, taxable amount, add-ons, reseller discount. You can change a line total, and yet the **grand total** is worked out again in the next stage.

$user_idintThe order's owner.

$user_currencyintThe order currency.

$dealershiparrayThe customer's reseller settings, resolved.

Return 1

voidThe value changes **by reference**; the return is not read.

Listener PHP

```php
Hook::add('filter:order.cart_items', 10,
    function (&$items, $user_id, $user_currency, $dealership) {
        // Change a line price here; the GRAND total is built in the next stage.
        foreach ($items as &$i)
            if (Acme::bundleEligible($i)) $i['price'] = Acme::bundlePrice($i);
    });
```

### Changing the cart summary

filterorder.cart_totals

`Orders::buildCart()` the last link

Runs after the price summary was built — the **final** figures the customer sees are here.

Parameters 5

$summaryarrayrefThe price summary: the subtotal, the display subtotal, the reseller discount, the coupon discount, tax. The **end** of the chain: these figures reach the customer and the invoice.

$itemsarrayThe priced lines as the previous filter left them. Context you cannot change.

$subtotalfloatThe subtotal before discounts and tax.

$selectedCouponsarrayThe coupons in play. Fixed amounts are **already converted** into the customer's currency.

$ctxarrayThe pricing context: currency, taxation type, tax rates, exemption.

Return 1

voidThe value changes **by reference**; the return is not read.

Listener PHP

```php
Hook::add('filter:order.cart_totals', 10,
    function (&$summary, $items, $subtotal, $selectedCoupons, $ctx) {
        // The END of the chain: what you write here is what the customer sees.
        if (Acme::minimumOrder() > (float) $summary['subtotal'])
            $summary['acme_notice'] = Acme::minimumNotice();
    });
```

### Stopping the checkout step

gateorder.checkout

`ClientCheckout` the lines are unpriced

Runs before the order is placed. **A guest never reaches here**: the sign-in check runs earlier.

Parameters 2

$memberarrayThe signed-in member's record.

$cartItemsarrayThe raw cart lines. **Not priced yet**: for a rule that depends on an amount, work the prices out yourself or use the summary filter.

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.checkout', 10, function ($member, $cartItems) {
    // The lines are NOT priced yet: keep amount-based rules out of here.
    if (Acme::fraudScore((int) ($member['id'] ?? 0)) > 80)
        return 'Your order wants a review; our team will be in touch.';

    return null;
});
```

### Learning that an order was placed

actionorder.checkout_completed

`ClientCheckout` the invoice is unpaid

Runs after the order was placed. Its invoice is **unpaid** at this point.

Parameters 4

$order_idintThe id of the placed order.

$invoice_idintThe order's invoice. Unpaid right now; a zero-total order or one settled from the balance closes **after this hook** in the same request.

$pmethodstringThe resolved payment method. On a zero-total order `Free`; on a stored card the real gateway module's name.

$memberarrayThe record of the member who ordered.

Return 1

voidThe return is ignored.

Listener PHP

```php
Hook::add('action:order.checkout_completed', 10,
    function ($order_id, $invoice_id, $pmethod, $member) {
        // The invoice is NOT paid yet: leave opening services to the payment hook.
        Crm::orderPlaced($order_id, $pmethod, (string) ($member['email'] ?? ''));
    });
```

### Changing the coupon discount

filterorder.coupon_discount

`Orders` the computed discount

Runs after the coupon discount was worked out.

Parameters 1

$discountarrayrefThe computed discount. It feeds the cart summary and runs **before** the summary filter.

Return 1

voidThe value changes **by reference**; the return is not read.

Listener PHP

```php
Hook::add('filter:order.coupon_discount', 10, function (&$discount) {
    Acme::capCouponDiscount($discount);
});
```

### Changing the order number

filterorder.number

`Orders` it has to be unique

Runs after the order number was produced.

Parameters 1

$numberstringrefThe number produced. Changing it, **guarantee uniqueness yourself**: the core does not check what you produced.

Return 1

voidThe value changes **by reference**; the return is not read.

Listener PHP

```php
Hook::add('filter:order.number', 10, function (&$number) {
    // Uniqueness is YOURS to keep: a clash breaks the order.
    $number = Acme::prefix() . '-' . $number;
});
```

## Pitfalls

> **The wrong link in the chain is quietly overwritten**
> 
> Pricing runs in three stages: inputs, lines, summary. Touching the grand total at the line stage is wasted — the summary stage **works it out again**. A line price belongs on the item filter, a final figure on the summary filter, and a new product on the **input filter**.

> **The lines are unpriced at the checkout gate**
> 
> The checkout gate hands you the **raw** cart lines: no amounts, discounts or tax yet. Writing "stop orders above this amount" here means reading **a field that is not there**.

> **Placing an order is not paying for it**
> 
> On the order-completed hook the invoice is **unpaid**. Opening a service, switching an add-on on or delivering to the customer here means an order that never pays is **delivered anyway**.

> **Change the number and its uniqueness is yours**
> 
> The order number filter hands you a number already produced. Once you change it the core **does not check it again**: produce a clashing one and the order record breaks. Adding your own prefix is safe; producing the number from scratch is not.

## Related Articles

- Order Flow Hooks
- [Invoice Lifecycle Hooks](https://dev.wisecp.com/en/invoice-lifecycle-hooks)
- [Currency and Coupon Hooks](https://dev.wisecp.com/en/currency-and-coupon-hooks)
