# How To / Determine Product Prices

# How-To: Determine Product Prices

## How Shopfront Stores Prices

Shopfront is exceptionally customisable for pricing products. This can lead to some confusion when developing 
applications as the price displayed on the register may not be the same as the one initially developed.

If you're building a generic application for a lot of Vendors, care should be taken to match (or at least advise stores)
of the settings and how your application will calculate prices.

If you're building for a single Vendor, you should discuss with that store how they would like their prices to come out.

> Your application doesn't need to support every possible combination of pricing, but you should make it clear to your
> users how your application will calculate prices.

Prices for products should be loaded from the [`Product`](/documentation/Objects/Products#Product) GraphQL object. 
Specifically, you should use the `prices` field, which returns a list of 
[`ProductPrice`](/documentation/Objects/Products#ProductPrice) objects.

* `quantity`: The quantity required to trigger this price,
* `price`: The price set by the store for the quantity and price set,
* `priceSet`: The [Price Set](#price-sets) this price belongs to

For example, you might have a product like Pure Blonde 375ml cans, this item is typically sold as a single, a six-pack,
and a carton. So it might have prices set as 1 for $4, 6 for $16, and 24 for $50.

We call the lowest quantity the _base price_ and the price being used (e.g. 1 for $4, 2 for $8, or 6 for $16) the 
_everyday price_.

> There are other fields available on the `ProductPrice` object, consult the documentation page to find out more

**Example Prices Query**

```graphql
query GetProducts {
    products {
        edges {
            node {
                prices {
                    quantity,
                    price,
                    priceSet {
                        id,
                        name,
                    },
                },
            },
        },
    },
}
```

**Example Prices Query**

```graphql
query GetProducts {
    products {
        edges {
            node {
                prices {
                    quantity,
                    price,
                    priceSet {
                        id,
                        name,
                    },
                },
            },
        },
    },
}
```

## Alternatives to Everyday Prices

Stores have a number of other options other than just setting the everyday price, these include:

- Promotions: Discounts or overrides to the everyday price which will be displayed to the customer as savings. These 
  must be lower than the everyday price to trigger,
    - [General Support Documentation](https://support.onshopfront.com/hc/en-us/sections/360000424291-All-About-Promotions)
    - [Types](/documentation/Objects/Promotions)
    - [Promotions can also be provided back for a matching barcode quantity](/documentation/Objects/Products#ProductBarcode)
- Price Lists: Customer specific pricing, these can be a price adjustment (e.g. discount 10%) or can be something like
  a price override. These are not displayed to customers as a savings as we treat them as specific pricing for those 
  customers,
    - [Types](/documentation/Objects/Price-Lists)    
- Surcharging: Whilst not directly the setting of the price, Shopfront supports public-holiday surcharging where a fixed
  percentage or amount can be applied to items to increase the price,
    - [General Support Documentation](https://support.onshopfront.com/hc/en-us/articles/5362031787661-How-to-Activate-Surcharging)
    - [Types](/documentation/Objects/Register#SurchargeRule)
- Family Pricing: Shopfront has a special category called families, these allow for mixing and matching items as if they
  were the same product. Typically you'd group together products that share the same branding and size. Prices must be
  the same for the prices within a family,
    - [General Support Documentation](https://support.onshopfront.com/hc/en-us/articles/360000071531-Classifications#h_01J1RNNH4WZM2BPK46AN9FRS7K)
    - [Types](/documentation/Objects/Classifications#Family)
- Future Prices: More a feature than a specific way of affecting the price, Shopfront supports the ability to set
  prices to change in the future
    - [General Support Documentation](https://support.onshopfront.com/hc/en-us/articles/360052806431-Future-Prices)
    - [Types](/documentation/Objects/Products#FuturePrice)

## Price Sets

Shopfront supports a concept called _Price Sets_ which allows stores to sell products at different prices in different 
locations. These locations can be something like having a bar price vs a bottleshop price, location A vs location B, 
in-store vs online or whatever the store would prefer to support.

> If you're building an eCommerce store or a ticketing system, we would highly suggest allowing the store to select 
> which price set they would like to use within your settings. This can potentially be per-Outlet or it may be a global
> selection for your application.

All stores internally support price sets, but being able to define new price sets is only available on the premium plan.

Every store has a price set called "Default Prices", in the API this is indicated by the `priceSet` field for the 
`ProductPrice` type returning `null`.

Price Sets completely override their parent item. For example if you have a Default Price Set of 1 for $4 and 4 for $10
plus a second price set with prices set as 1 for $5, 2 for $7, buying 4 is not expected to provide you with a $10 price
as they don't interleave.

### Inheritance

Price Sets support inheritance where the store can select which price set they would like to override, this is done on
a per-price-set level (not per-product). You might have a setup which looks something like:

```text
             +-----------------+
             |  Default Price  |
             +-----------------+
               /             \
              /               \
             v                 v
+-----------------+     +-----------------+
| Store Group #1  |     | Store Group #2  |
| (Price Level A) |     | (Price Level A) |
+-----------------+     +-----------------+
         |                       |
         v                       v
+-----------------+     +-----------------+
| Expanded Prices |     | My Second Group |
| (Price Level B) |     |  Moonee Ponds   |
+-----------------+     +-----------------+
         |
         v
+-----------------+
| My Banner Group | 
|    Brunswick    |
+-----------------+
```

You might then have a product setup something like this:

| Price Set       | Price |
|-----------------|-------|
| Default Price   | $10   |
| Store Group #1  | $11   |
| Expanded Prices | $12   |
| Moonee Ponds    | $14   |
| Brunswick       | $9    |

Selling this item at Brunswick would cost $9. Selling it at Moonee Ponds would cost $14, and if you had another store
which inherited from Store Group #2, this would cost $10 (as Store Group #2 isn't overriding the default price).

In the code examples on the right-hand side of the page (on desktop) you can view a basic version of the algorithm we
use internally to determine the price to use.

**Price Set Inheritance**

```javascript
const prices = [{
    quantity: 1,
    price: 4,
    priceSet: null,
}, {
    quantity: 4,
    price: 10,
    priceSet: null,
}, {
    quantity: 1,
    price: 5,
    priceSet: "11e6..."
}, {
    quantity: 2,
    price: 7,
    priceSet: "11e6...",
}];

const parents = {
    "11e6...": null,
    "11e7...": "11e6...",
};

let wantedPriceSet = "11e7...";
const pricesToUse = [];

while(wantedPriceSet !== false) {
    for(let i = 0, l = prices.length; i < l; i++) {
        if(prices[i].priceSet === wantedPriceSet) {
            pricesToUse.push(prices[i]);
        }
    }

    if(pricesToUse.length) {
        break;
    }

    if(wantedPriceSet === null) {
        wantedPriceSet = false;
    } else {
        wantedPriceSet = parents[wantedPriceSet] ?? null;
    }
}

// `pricesToUse` will now contain the prices that match the
// originally wanted price set
```

**Price Set Inheritance**

```php
<?php

$prices = [[
    "quantity" => 1,
    "price" => 4,
    "priceSet" => null,
], [
    "quantity" => 4,
    "price" => 10,
    "priceSet" => null,
], [
    "quantity" => 1,
    "price" => 5,
    "priceSet" => "11e6...",
], [
    "quantity" => 2,
    "price" => 7,
    "priceSet" => "11e6...",
]];

$parents = [
    "11e6..." => null,
    "11e7..." => "11e6...",
];

$wantedPriceSet = "11e7...";
$pricesToUse = [];

while($wantedPriceSet !== false) {
    for($i = 0, $l = count($prices); $i < $l; $i++) {
        if($prices[$i]["priceSet"] === $wantedPriceSet) {
            $pricesToUse[] = $prices[$i];
        }
    }

    if(count($pricesToUse) > 0) {
        break;
    }

    if($wantedPriceSet === null) {
        $wantedPriceSet = false;
    } else {
        $wantedPriceSet = $parents[$wantedPriceSet] ?? null;
    }
}

// `$pricesToUse` now contains the prices that match the originally
// wanted price set (or its first ancestor with prices)
```

## Settings That Affect Pricing

As mentioned earlier, Shopfront has a number of settings that affect how prices are both calculated and distributed.

General information and in-depth examples of how these work can be found in our 
[support centre's documentation](https://support.onshopfront.com/hc/en-us/articles/360028652052-Setting-Prices-and-Configuring-Price-Calculation).

The two main settings that may affect how your integration works are Quantity Rate and Cross Promotion Count. The most
up-to-date information on how Cross Promotion Count works can be found in our support centre.

### Quantity Rate / High Mix Price

If a store is set to use _Quantity Rate_ (in the general settings, the Use Quantity Rate toggle is enabled) products 
that are sold after they have passed the previous price point will continue selling at the same rate. 

If a store is set to use _High Mix Price_ (in the general settings, when the Use Quantity Rate toggle is disabled)
products that are sold after they have passed the previous price point will recalculate from the base price (as if the
products were sold separately).

Imagine you have a product with the following price points:

| Quantity | Price  |
|----------|--------|
| 1        | $3.50  |
| 6        | $15.00 |
| 24       | $50.00 |

If _Quantity Rate_ is enabled, the price for the quantity of 7 will be $17.50

- $15 divided by 6 which equals $2.50. Multiply $2.50 by the quantity of 7

If _High Mix_ is enabled, the price for the quantity of 7 will be $18.50

- $15 for the quantity of 6 plus $3.50 for the quantity of 1

On the right-hand side of the page (desktop) you can view basic examples of algorithms we use to calculate the price.

> Shopfront's calculation is significantly more complex than the examples to the right, however, for most applications 
> the algorithm should be enough to cover 99% of cases.

**Price Calculation**

```javascript
const calculateQuantityRate = (prices, quantity) => {
    if(!prices.length) {
        return 0;
    }

    let currentRate = prices[0].price / prices[0].quantity;

    for(let j = 1, m = prices.length; j < m; j++) {
        if(prices[j].quantity <= quantity) {
            const newRate = prices[j].price / prices[j].quantity;

            if(newRate < currentRate) {
                currentRate = newRate;
            }
        } else {
            break;
        }
    }

    return currentRate * quantity;
};

const calculateHighMixPrice = (prices, quantity) => {
    // NOTE: Internally Shopfront uses a dynamic Knapsack calculation
    // for this as it produces the best result, however the concept
    // can be completed by the below
    let priceIndex = prices.length - 1;

    if(priceIndex === -1) {
        return 0;
    }

    let tempQuantity = quantity;
    let currentPrice = 0;

    while(tempQuantity > 0 && priceIndex >= 0) {
        if(prices[priceIndex].quantity <= tempQuantity) {
            currentPrice += prices[priceIndex].price;
            tempQuantity -= prices[priceIndex].quantity;
        } else {
            priceIndex -= 1;
        }
    }

    if(tempQuantity > 0) {
        currentPrice += prices[0].price / prices[0].quantity * tempQuantity;
    }

    return currentPrice;
};
```

**Price Calculation**

```php
<?php

function calculateQuantityRate(
    array $prices,
    int|float $quantity
): float {
    if(count($prices) === 0) {
        return 0.0;
    }

    $currentRate = $prices[0]["price"] / $prices[0]["quantity"];

    for($j = 1, $m = count($prices); $j < $m; $j++) {
        if($prices[$j]["quantity"] <= $quantity) {
            $newRate = $prices[$j]["price"] / $prices[$j]["quantity"];

            if($newRate < $currentRate) {
                $currentRate = $newRate;
            }
        } else {
            break;
        }
    }

    return $currentRate * $quantity;
}

function calculateHighMixPrice(
    array $prices,
    int|float $quantity
): float {
    // NOTE: Internally Shopfront uses a dynamic Knapsack calculation for
    // this as it produces the best result, however the concept can be
    // completed by the below
    $priceIndex = count($prices) - 1;

    if($priceIndex === -1) {
        return 0.0;
    }

    $tempQuantity = $quantity;
    $currentPrice = 0.0;

    while($tempQuantity > 0 && $priceIndex >= 0) {
        if($prices[$priceIndex]["quantity"] <= $tempQuantity) {
            $currentPrice += $prices[$priceIndex]["price"];
            $tempQuantity -= $prices[$priceIndex]["quantity"];
        } else {
            $priceIndex -= 1;
        }
    }

    if($tempQuantity > 0) {
        $currentPrice += $prices[0]["price"] / $prices[0]["quantity"] * $tempQuantity;
    }

    return $currentPrice;
}
```