# Services

# What is the tax accountant service?

The Tax Account is the service responsible for determining the tax multiplier that should be applied to a product price.

It uses the store's tax configuration together with the current customer's tax information and location to determine which tax rules and rates apply. The service can then use those multipliers to convert prices between tax-exclusive and tax-inclusive values.

## Converting prices

### Ex =&gt; Inc

The `priceInc` method can be used to convert an ex price to an inc price, we use it when handling prices for products:

```php
app('aero.accountant')->priceInc($value, $this->product_tax_group_id);
```

### Inc =&gt; Ex

The `priceEx` method can be used to convert an inc price to an ex price, we use it when handling prices for products:

```php
app('aero.accountant')->priceEx($value, $this->product_tax_group_id);
```

## Resolvers

### Tax Group Resolver

The Tax Account uses a tax group resolver to resolve a customer's tax group id.

A resolver is set by the `Aero\Store\Http\Middleware\SetTaxResolver` middleware:

```php
$this->accountant->setTaxGroupResolver(static function (?\Aero\Account\Models\Customer $customer) {
    if ($customer && ($group = $customer->getAttribute('customer_tax_group_id'))) {
        return $group;
    }

    if ($customer && $customer->customer_group_id && ($group = $customer->group->getAttribute('customer_tax_group_id'))) {
        return $group;
    }

    return null;
});
```

<p class="callout info">If no customer tax group id is resolved, the store's default will be used.</p>

### Country Resolver

The Tax Account uses a country resolver to resolve the country it should use.

A resolver is set by the `Aero\Store\Http\Middleware\SetTaxResolver` middleware:

```php
$this->accountant->setCountryResolver(static function (?\Aero\Account\Models\Customer $customer) {
    if ($cartLocale = \Aero\Cart\Facades\Cart::instance(PaymentDriver::MOTO)->locale()) {
        return $cartLocale->countryCode;
    }
    
    return null;
});
```

<p class="callout info">If no country code is resolved, the store's default will be used.</p>

### Zone Resolver

The Tax Account uses a country resolver to resolve the country it should use.

A resolver is set by the `Aero\Store\Http\Middleware\SetTaxResolver` middleware:

```php
$this->accountant->setZoneResolver(static function (?\Aero\Account\Models\Customer $customer) {
    if ($cartLocale = \Aero\Cart\Facades\Cart::instance(PaymentDriver::MOTO)->locale()) {
        return $cartLocale->zoneId;
    }
    
    return null;
});
```

<p class="callout info">If no zone id is resolved, then no zone id will be used.</p>

### Postcode Resolver

The Tax Account uses a country resolver to resolve the country it should use.

A resolver is set by the `Aero\Store\Http\Middleware\SetTaxResolver` middleware:

```php
$this->accountant->setPostcodeResolver(static function (?\Aero\Account\Models\Customer $customer) {
    if ($cartLocale = \Aero\Cart\Facades\Cart::instance(PaymentDriver::MOTO)->locale()) {
        return $cartLocale->postcode;
    }
    
    return null;
});
```

<p class="callout info">If no postcode is resolved, then no postcode will be used.</p>

# What is the combination serializer service?

The combination serializer service is used to serialize and deserialize models.

### Serializing

This is used to take a collection of models and convert them into a string that can be used to find a corresponding combination.

Let's say you have a category called `mens`, a manufacturer called `acme` and a `blue` tag in the group `color`. Then if you visit `/mens/acme/blue` on the storefront it will load any products in the category `mens` that have the manufacturer `acme` and are tagged with `blue`.

Perhaps you might rather that it 404s unless a combination for those models exists (as otherwise there may very likely be no products that matches the criteria). This can be done by serializing the slugs and considering them invalid if they don't correspond to an existing combination model:

```php
\Aero\Store\Routing\Slugs::setValidator(function (\Illuminate\Database\Eloquent\Collection $slugs, $resolver) {
    if ($resolver !== 'listings') return true;

    try {
        $serialized = \Aero\Common\Services\CombinationSerializer::serialize(
            $slugs->map(function (\Aero\Store\Models\Slug $slug) {
                return $slug->model;
            })
        );

        return \Aero\Catalog\Models\Combination::where('serialized', $serialized)->exists();
    } catch (\Exception $e) {
        return false;
    }
});
```

### Deserializing

This is used to take a combination model and find all of its' component models (collections, categories, manufacturers &amp; tags).

For example, you might want to resolve the category ids for the listings page from the combination:

```php
\Aero\Store\Http\Responses\ListingsPage::extend(function (\Aero\Store\Http\Responses\ListingsPage $page, \Closure $next) {
    $categoryIds = \Aero\Common\Services\CombinationSerializer::deserialize($page->getData('combination'))
        ->filter(function (\Illuminate\Database\Eloquent\Model $model) {
            return $model instanceof \Aero\Catalog\Models\Category;
        })
        ->pluck('id');

    // Do stuff...

    return $next($page);
});
```

# What is the login blocker service?

The login blocker service can be used to block customers from logging in based on criteria.

For example, you might want to blacklist certain IPs.

First, define a setting to store blacklisted IPs:

```php
\Aero\Common\Facades\Settings::group('customers', static function (\Aero\Common\Settings\SettingGroup $group) {
    $group->array('blocked_ip_addresses');
});
```

Then add a login blocker to check if the customer IP is blocked:

```php
\Aero\Common\Facades\LoginBlocker::add(function (\Aero\Account\Models\Customer $customer) {
    if (in_array(request()->ip(), setting('customers.blocked_ip_addresses')) {
        return __('Blocked due to suspicious activity');
    }  
});
```

<p class="callout info">Returning a string means that they customer will be blocked from logging in and that string will be displayed.</p>

# What is the volume service?

The volume service is used to normalize volumes into the store's base unit (`cm^3` by default) as well as convert between different units.

## Units

By default the volume service has the following units:

- `mm^3`
- `cm^3`
- `m^3`

### Adding a Unit

Use `addUnit()` to add a unit, for example we can add millilitres &amp; litres:

```php
\Aero\Common\Facades\Volume::addUnit('ml', 'ml');

\Aero\Common\Facades\Volume::addUnit('l', 'L')
    ->setNormalizer(function ($value) {
        return $value * 1000;
    });
```

<p class="callout info">`setNormalizer` defines how to get from that unit to the base unit, which is cm^3.</p>

### Removing a Unit

```php
\Aero\Common\Facades\Volume::removeUnit('m^3');
```

### Converting between Units

You can use `convert()` to convert between any two units, or `normalize()` to convert into the store's base unit.

```php
\Aero\Common\Facades\Volume::convert(1000, 'mm^3', 'cm^3');

\Aero\Common\Facades\Volume::normalize('mm^3', 1000);
```

### Setting a base Unit

```php
\Aero\Common\Facades\Volume::setBaseUnit('ml');
```

<p class="callout info">When you change base unit, database values will still remain in cm^3. But volumes will be displayed in the new unit on screens such as the fulfillment create page.</p>

# What is the weight service

The weight service is used to normalize weights into the store's base unit (`g` by default) as well as convert between different units.

## Units

By default the weight service has the following units:

- `g`
- `oz`
- `lb`
- `kg`

### Adding a Unit

Use `addUnit()` to add a unit, for example we can add milligrams:

```php
\Aero\Common\Facades\Weight::addUnit('mg', 'mg')
    ->setNormalizer(function ($value) {
        return $value / 1000;
    });
```

<p class="callout info">`setNormalizer` defines how to get from that unit to the base unit, which is `g`.</p>

### Removing a Unit

```php
\Aero\Common\Facades\Weight::removeUnit('kg');
```

### Converting between Units

You can use `convert()` to convert between any two units, or `normalize()` to convert into the store's base unit.

```php
\Aero\Common\Facades\Weight::convert(1000, 'oz', 'g');

\Aero\Common\Facades\Weight::normalize('oz', 1000);
```

### Setting a base Unit

```php
\Aero\Common\Facades\Weight::setBaseUnit('oz');
```

<p class="callout info">When you change base unit, database values will still remain in grams. But weights will be displayed in the new unit on screens such as the fulfillment create page.</p>