# n-pricing
### 💷 Consistent currency data, pricing and transformations

Pricing needs to remain consistent across the site, from barrier, to marketing popup, to signup confirmation page.

This small library allows the validation and manipulation of currencies and pricing, and performs pricing transformations.

## Classes

- `Price`
- `Monthly`
- `Quarterly`
- `Yearly`

## Installation

```sh
npm install --save @financial-times/n-pricing
```

***

## Price

```js
const Price = require('@financial-times/n-pricing').Price;
```

### Usage

```js
new Price().get()
// { symbol: 'GBP', nativeSymbol: 'GBP', currency: 'GBP', value: 'GBP0.00', _rawValue: {} }

new Price({ value: 1 }).get()
// { symbol: 'GBP', nativeSymbol: 'GBP', currency: 'GBP', value: 'GBP1.00', _rawValue: 1 }

new Price({ value: 1, symbol: '£' }).get() // £1

new Price({ value: 1, currency: 'JPY' }).getAmount() // JPY1

new Price({ value: 1, currency: 'JPY', symbol: '¥' }).getAmount() // ¥1

new Price({ value: 1, currency: 'HKD', nativeSymbol: '$' }).price
// { amount: { symbol: 'HKD', nativeSymbol: '$', currency: 'HKD', value: 'HKD1.00', _rawValue: 1 }

new Price({ value: 1, currency: 'HKD', nativeSymbol: '$', trimDecimalsWhenWhole: true }).price
// { amount: { symbol: 'HKD', nativeSymbol: '$', currency: 'HKD', value: 'HKD1.00', _rawValue: 1 }
```

### Discounting

```js
new Price({ value: 1 }).discount( -0.5 ).get() 
// {  symbol: 'GBP', nativeSymbol: 'GBP', currency: 'GBP', value: 'GBP0.50', _rawValue: 1 }

new Price({ value: 1, symbol: '£' }).discount( -0.5 ).getAmount() // £0.50
```

### Comparing

The `.compare()` method returns savings in value difference and as a percentage.

Negative values indicate a **markup**, *i.e. the stored value of the object is higher than the value we are comparing against.*

```js
new Price({ value: 10 }).compare( 8 ) // { value: '-GBP2.00', percentage: '-20%' }

new Price({ value: 8, symbol: '£'}).compare( 10 ) // { value: '£2.00', percentage: '25%'}
```

### Formatted amounts

The `.getAmountFormatted()` method returns values formatted based on the options.

The options are:

- `trimDecimalsWhenWhole` - Boolean that determines whether to remove the decimal part of the amount if it is a whole number. Default value is `false`.

```js
new Price({ value: 10 }).getAmountFormatted({ trimDecimalsWhenWhole: true }); // Returns 'GBP10'
new Price({ value: 10.25, symbol: '£' }).getAmountFormatted({ trimDecimalsWhenWhole: true }); // Returns '£10.25'
```

### Uninterpolated

You're able to return pricing and percentage information without symbols interpolated by passing in an `uninterpolated` flag as follows:

*NB:* Only use this if you have some specific reason that falls outside the remit of this module.

```js
new Price({ value: 1 }, { uninterpolated: true }).get()
// { symbol: 'GBP', nativeSymbol: 'GBP', currency: 'GBP', value: '1.00', _rawValue:  1 }

new Price({ value: 1, currency: 'JPY' }, { uninterpolated: true }).getAmount() // 1.00

new Price({ value: 1 }, { uninterpolated: true }).discount( -0.5 ).get()
// { symbol: 'GBP', nativeSymbol: 'GBP', currency: 'GBP', value: '0.50', _rawValue: 1 }

new Price({ value: 10 }, { uninterpolated: true }).compare( 8 ) // { value: '-2.00', percentage: '-20'}
```

### getMonthlyEquivalentIf90DaysOrLonger

Returns the monthly equivalent price only when the original billing period is approximately **90 days or longer**.

This is useful when displaying a monthly equivalent for longer-term offers such as quarterly or yearly pricing, while avoiding monthly equivalents for shorter periods such as one month. The agreed business logic with RRG (Reader Revenue Group) has been that the cut-off is 90 days or less.

If the billing period is shorter than 90 days, the method returns `null`.

```js
new Price({ value: 30 }, { period: 'P1M' }).getMonthlyEquivalentIf90DaysOrLonger()
// null

new Price({ value: 90 }, { period: 'P3M' }).getMonthlyEquivalentIf90DaysOrLonger()
// {
//   amount: {
//     symbol: 'GBP',
//     nativeSymbol: 'GBP',
//     currency: 'GBP',
//     value: 'GBP30.00',
//     _rawValue: '30'
//   }
// }

new Price({ value: 120, symbol: '£' }, { period: 'P1Y' }).getMonthlyEquivalentIf90DaysOrLonger()
// {
//   amount: {
//     symbol: '£',
//     nativeSymbol: '£',
//     currency: 'GBP',
//     value: '£10.00',
//     _rawValue: '10'
//   }
// }

new Price({ value: 84, symbol: '£' }, { period: 'P12W' }).getMonthlyEquivalentIf90DaysOrLonger()
// null

new Price({ value: 90, symbol: '£' }, { period: 'P90D' }).getMonthlyEquivalentIf90DaysOrLonger()
// {
//   amount: {
//     symbol: '£',
//     nativeSymbol: '£',
//     currency: 'GBP',
//     value: '£30.00',
//     _rawValue: '30'
//   }
// }
```

***

## Price transformations

### Monthly, Yearly & Quarterly

Offer pricing is either annual, monthly or quarterly. We often display pricing translated into its weekly cost.

`Monthly`, `Yearly` and `Quarterly` extend the `Price` class to offer some useful pricing transformations that allow for all alternative breakdowns of cost.

```js
const Monthly = require('@financial-times/n-pricing').Monthly;
const Yearly = require('@financial-times/n-pricing').Yearly;
const Quarterly = require('@financial-times/n-pricing').Quarterly;
```

### Usage

```js
new Monthly({ value: 1 }).get('yearly')
// { symbol: 'GBP', nativeSymbol: 'GBP', currency: 'GBP', value: 'GBP12.00' _rawValue: '12' }

new Yearly({ value: 96.45 }).get('monthly')
// { symbol: 'GBP', nativeSymbol: 'GBP', currency: 'GBP', value: 'GBP8.04', _rawValue: '8.0375' }

new Quarterly({ value: 30 }).get('yearly')
// { symbol: 'GBP', nativeSymbol: 'GBP', currency: 'GBP', value: 'GBP120.00', _rawValue: '120' }

new Monthly({ value: 1, currency: 'OMR', nativeSymbol: '﷼' }).getAmount('weekly') // OMR0.24

new Monthly({ value: 1, symbol: '£' }).price
/*
{
  yearly: {
    amount: { symbol: '£', nativeSymbol: '£', currency: 'GBP', value: '£12.00', _rawValue: '12' }
  },
  quarterly: {
    amount: { symbol: '£', nativeSymbol: '£', currency: 'GBP', value: '£3.00', _rawValue: '3' }
  },
  monthly: {
    amount: { symbol: '£', nativeSymbol: '£', currency: 'GBP', value: '£1.00', _rawValue: 1 }
  },
  weekly: {
    amount: { symbol: '£', nativeSymbol: '£', currency: 'GBP', value: '£0.24', _rawValue: '0.230769230...' }
  },
  daily: {
    amount: { symbol: '£', nativeSymbol: '£', currency: 'GBP', value: '£0.04', _rawValue: '0.032876712...' }
  }
}
*/
```

### Comparing savings

```js
let yearlyPrice = new Yearly({ value: 10 });
let monthlyPrice = new Monthly({ value: 1 });

yearlyPrice.compare( monthlyPrice.get('yearly') )
// { value: 'GBP2.00', percentage: '20%'}
// annual pricing at £10 offers a 20% saving over paying £1 a month
```

## How exactly are pricing transformations made?

We use [`big.js`](http://mikemcl.github.io/big.js/) to handle decimal arithmetic.

We use the `ROUND_UP` rounding method in our calculations: http://mikemcl.github.io/big.js/#rm
> Rounds away from zero.

Offers originally have their priced expressed as either `x per Year`, `x per Month` or `x per Quarter`. From this we may want to display an expression of the price `weekly` or `daily`.

Using the table below. input `12 per Year` would be displayed as output `1 per Month`.


| Input Original Price Expression (val) | Output Expression | Calculation    |
| ------------------------------------- | ----------------- | -------------- |
| Yearly                                | Monthly           | `val/12`       |
|                                       | Quarterly         | `val/4`        |
|                                       | Weekly            | `val/52`       |
|                                       | Daily             | `val/365`      |
| Monthly                               | Yearly            | `val*12`       |
|                                       | Quarterly         | `val*3`        |
|                                       | Weekly            | `(val*12)/52`  |
|                                       | Daily             | `(val*12)/365` |
| Quarterly                             | Yearly            | `val*4`        |
|                                       | Monthly           | `val/3`        |
|                                       | Weekly            | `val/13`       |
|                                       | Daily             | `(val*4)/365` |

***

## Local development

This package is a library (not a runnable app). To work on it locally and try its features against your changes:

From the repo root, run the scratch script (it requires the local package, not the published npm version):

```sh
node scratch.js
```

Edit `scratch.js` to try other API calls. See the sections above for more examples.
