# Injecting product prices and displaying a total price

The SKIK configurator has built-in facilities to automatically calculate total prices depending on the parts selected by a configurator user. In order for the configurator to do this, the prices must be explicitly given to a `Client` instance.

## Global rules concerning the expression of prices

- All prices are expected to be in the [**EUR** currency](https://en.wikipedia.org/wiki/Euro) at all times.

- All prices are expected to **include** [VAT](https://en.wikipedia.org/wiki/Value-added_tax).  
  _Value-added Tax, BTW in the Netherlands._

- All price values are expected to be quantified in the **cents unit**.  
  _For example: **EUR 12,50** is expressed as `1250`.  
  In code, prices are at all times expected to be of type `Number`._

- 2 prices are used for all products:
    - **Original price**
      - **Is** the price for a product, **excluding any discount**.
      - **Is** by convention named `originalPrice` in code.

    - **Discount price**
      - **Is** the price for a product, **including potential discount**.
      - **Is** by convention named `discountPrice` in code.

## Expressing price discounts, or absence thereof

If a certain product has no applicable discount, **discount-price** must for that product equal **original-price**. In any case, both properties are required to keep API signatures consistent:

```typescript
const productWithDiscount: SKIKClient.ProductPrices = {
    EANCode: "8714713074655",
    originalPrice: 1250,
    discountPrice: 1050,
};

// Products without discount have the `discountPrice`
// value set to the same value as `originalPrice`:

const productWithoutDiscount: SKIKClient.ProductPrices = {
    EANCode: "8714713074662",
    originalPrice: 1600,
    discountPrice: 1600,
};
```

## Injecting product prices

**Only if prices for _all products_ are given, the configurator will attempt to calculate the sum of prices for any given configuration.**

If price-data is not given, or is incomplete, no relative prices can be shown in the configurator interface, and no [`"price-changed"` events](../api/event-channels#price-changed) can be dispatched by the client instance.

### Retrieving EAN codes for all relevant products

The [`Client # getEANCodes()`](../api/client#getting-an-array-of-all-product-ean-codes) method can be used to obtain an array of all EAN codes for products that are used in the SKIK configurator. The method returns a `Promise` that resolves to the array of codes:

```typescript
const skikClient: SKIKClient.Client = SKIKClient.create({ /* ... */ });
const EANCodesPromise: Promise<string[]> = skikClient.getEANCodes();

/* =>
    [
        "8714713074655",
        "8714713074662",
        "8714713074679",
        // ...
    ];
    */
```

_After retrieval, this returned array can be used as source for your price data query._

Let's assume a function called `pricesForEANCode` exists, that returns an object implementing the [`SKIKClient.ProductPrices`](../interfaces/prices#productprices) interface: a plain object as returned in this **pseudo-implementation**:

_In reality, product prices would of course be dynamically determined based on the given EAN code, instead of literal numbers._

```typescript
const pricesForEANCode = (code: string): SKIKClient.ProductPrices => ({
    EANCode,
    originalPrice: 1250,
    discountPrice: 1050,
});
```

### Feeding back all required prices into the configurator

With our price retrieval implementation in place, it's time to decorate the `EANCodes` array we retrieved a Promise for earlier. This example uses `pricesForEANCode` to perform a map operation on the array of EAN codes:

```typescript
EANCodesPromise.then(function(EANCodes) {

    const productData: SKIKClient.ProductPrices[] = EANCodes.map(function(code) {
        return pricesForEANCode(code);
    });

    // ...
})
```

The `productData` variable should now be in the exact format expected by [`Client # setProductPrices()`](../api/client#setting-the-prices-for-products), the method we'll use to inject the price data.

```typescript
// Like before:
EANCodesPromise.then(function(EANCodes) {

    const prices: SKIKClient.ProductPrices[] = EANCodes.map(function(code) {
        return pricesForEANCode(code);
    });

    // But with injection:
    skikClient.setProductPrices(prices);

    // The data is formatted like this:
    assert.equal(prices, [
        { EANCode: "8714713074655", originalPrice: 1250, discountPrice: 1050 },
        { EANCode: "8714713074662", originalPrice: 1600, discountPrice: 1600 },
        { EANCode: "8714713074679", originalPrice: 995, discountPrice: 995 },
        // ...
    ]);
    
});
```

## Displaying a total price

The configurator does not implement a display of the total configuration price. Instead, it emits the [`"price-changed"`](../interfaces/event-channels#price-changed) event each time a mutation of the total price occurred. We can listen to these events to get [price mutation reports](../interfaces/prices#pricemutationreport) providing valuable information about the old and new prices:

```typescript
skikClient.on("price-changed", (mutationReport) => {

    // Example data:
    assert.equal(mutationReport, {
        before: {
            originalPrice: { cents: 1250, formatted: "€12,50" },
            discountPrice: { cents: 1050, formatted: "€10,50" },
        },
        after: {
            originalPrice: { cents: 2500, formatted: "€25,00" },
            discountPrice: { cents: 2100, formatted: "€21,00" },
        },
    });

    console.log(`The previous real price (including discount) was: `, mutationReport.before.discountPrice.formatted);
    // > "The previous real price (including discount) was: €10,50"

    console.log(`The new real price (including discount) is: `, mutationReport.after.discountPrice.formatted);
    // > "The new real price (including discount) is: €21,00"
});
```