> For the complete documentation index, see [llms.txt](https://developer.esw.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://developer.esw.com/pricing-advisor-api/resources/tutorials/currency-display-and-rounding.md).

# Currency Display & Rounding

The Pricing Advisor API returns all the information needed to both round and format prices correctly via two structures present in every category response:

* **`roundingConfigurations`** — defines how to round a raw calculated price to a market-appropriate value
* **`currencyDisplays`** — defines how to format and display the rounded price for the end customer

{% stepper %}
{% step %}

### Where These Configs Appear

Both `roundingConfigurations` and `currencyDisplays` are arrays nested inside each category object. They are **per-category and per-currency** — rounding and display rules can differ between product categories in the same country, and between currencies within the same category.

**In Standard Advice (`CategoryResponse`):**

```json
{
  "id": "ApparelAccessories",
  "estimatedDuty": 12.5,
  ...
  "roundingConfigurations": [ ... ],
  "currencyDisplays": [ ... ]
}
```

**In Multiplier Advice (`MultiplierCategoryResponse`):**

```json
{
  "id": "ApparelAccessories",
  "multipliers": [ ... ],
  "roundingConfigurations": [ ... ],
  "currencyDisplays": [ ... ]
}
```

When processing a price for a given category and currency, always select the matching entry from each array using `currencyIso`:

```rb
roundingConfig = category.roundingConfigurations.find(r => r.currencyIso === destinationCurrency)
displayConfig  = category.currencyDisplays.find(d => d.currencyIso === destinationCurrency)
```

{% endstep %}

{% step %}

### &#x20;`RoundingConfigurationResponse` Field by Field

```json
{
  "currencyIso": "USD",
  "currencyExponent": 2,
  "direction": "Up",
  "model": "Standard"
}
```

| Field              | Type    | Description                                                                          |
| ------------------ | ------- | ------------------------------------------------------------------------------------ |
| `currencyIso`      | string  | ISO 4217 currency code this rounding rule applies to, e.g. `"USD"`, `"JPY"`, `"EUR"` |
| `currencyExponent` | integer | The number of decimal places to round to — see Step 5                                |
| `direction`        | string  | The rounding direction: `"Up"`, `"Down"`, or `"Nearest"`                             |
| `model`            | string  | The rounding model: `"Standard"` or `"Psychological"`                                |
| {% endstep %}      |         |                                                                                      |

{% step %}

### `direction` Values

`direction` controls which way an ambiguous intermediate value is resolved.

| Value       | Meaning                                            | Example: 100.534 to 2dp |
| ----------- | -------------------------------------------------- | ----------------------- |
| `"Up"`      | Always round towards positive infinity (ceiling)   | → `100.54`              |
| `"Down"`    | Always round towards zero (floor)                  | → `100.53`              |
| `"Nearest"` | Round to the nearest representable value (half-up) | → `100.53`              |

**Why `"Up"` is most common in retail:** rounding up ensures the displayed price always covers the actual cost of the charge. Rounding down could result in the retailer collecting less than the duty or fee owed.

The maths for each direction at a given `currencyExponent` `n`:

```ruby
factor = 10 ^ n

Up:      rounded = ceil(price  × factor) / factor
Down:    rounded = floor(price × factor) / factor
Nearest: rounded = round(price × factor) / factor
```

{% endstep %}

{% step %}

### Applying Rounding

The `currencyExponent` is the key input to rounding. It defines the precision of the final price:

| `currencyExponent` | Rounding precision   | Example currencies                        |
| ------------------ | -------------------- | ----------------------------------------- |
| `0`                | Whole number only    | JPY, KRW, HUF, VND, CLP                   |
| `1`                | One decimal place    | MGA, MRU                                  |
| `2`                | Two decimal places   | USD, EUR, GBP, AUD, CAD (most currencies) |
| `3`                | Three decimal places | KWD, BHD, OMR, TND                        |

#### Rounding Formula

```ruby
factor  = 10 ^ currencyExponent

Up:      ceil(rawPrice  × factor) / factor
Down:    floor(rawPrice × factor) / factor
Nearest: round(rawPrice × factor) / factor
```

#### Worked Examples

**USD — exponent 2, direction Up:**

```ruby
rawPrice  = 100.534
factor    = 10^2 = 100
ceil(100.534 × 100) / 100
= ceil(10053.4) / 100
= 10054 / 100
= 100.54
```

**JPY — exponent 0, direction Up:**

```ruby
rawPrice  = 17583.51
factor    = 10^0 = 1
ceil(17583.51 × 1) / 1
= ceil(17583.51) / 1
= 17584
```

**KWD — exponent 3, direction Nearest:**

```ruby
rawPrice  = 30.4567
factor    = 10^3 = 1000
round(30.4567 × 1000) / 1000
= round(30456.7) / 1000
= 30457 / 1000
= 30.457
```

{% endstep %}

{% step %}

### `CurrencyDisplayResponse` Field by Field

```json
{
  "currencyIso": "USD",
  "currencySymbol": "$",
  "currencyExponent": 2,
  "decimalSeparator": ".",
  "thousandSeparator": ",",
  "showTrailingZeros": true,
  "configurationString": "$ #,##0.00"
}
```

| Field                 | Type    | Description                                                                          |
| --------------------- | ------- | ------------------------------------------------------------------------------------ |
| `currencyIso`         | string  | ISO 4217 currency code                                                               |
| `currencySymbol`      | string  | The currency symbol, e.g. `"$"`, `"€"`, `"¥"`, `"kr"`                                |
| `currencyExponent`    | integer | Number of decimal places to display                                                  |
| `decimalSeparator`    | string  | Character used between integer and decimal parts, e.g. `"."` or `","`                |
| `thousandSeparator`   | string  | Character used between groups of three digits, e.g. `","`, `"."`, or `" "`           |
| `showTrailingZeros`   | boolean | Whether to show `.00` when the fractional part is zero, e.g. `$100.00` vs `$100`     |
| `configurationString` | string  | A ready-made format pattern encoding symbol, separators, and precision — see Step 11 |
| {% endstep %}         |         |                                                                                      |

{% step %}

### Decimal and Thousand Separators Across Markets

#### Common Patterns

| Pattern                        | `decimalSeparator` | `thousandSeparator` | Example       | Markets                    |
| ------------------------------ | ------------------ | ------------------- | ------------- | -------------------------- |
| Period-decimal, comma-thousand | `"."`              | `","`               | `$1,234.99`   | US, UK, AU, CA, JP, CN     |
| Comma-decimal, period-thousand | `","`              | `"."`               | `1.234,99 €`  | DE, FR, IT, ES, NL, PT, PL |
| Comma-decimal, space-thousand  | `","`              | `" "`               | `1 234,99 kr` | SE, NO, DK, FI, CH (FR)    |
| Period-decimal, space-thousand | `"."`              | `" "`               | `1 234.99`    | CH (EN), CA (FR)           |

#### Euro Example

EUR is used across the eurozone, but different countries format it differently. The `currencyDisplays` config for Germany and France will differ from that of Ireland, even though all three use EUR.

| Country      | Formatted price |
| ------------ | --------------- |
| Germany (DE) | `1.234,99 €`    |
| France (FR)  | `1 234,99 €`    |
| Ireland (IE) | `€1,234.99`     |

This is why `currencyDisplays` is returned **per category per country**, not per currency globally.
{% endstep %}

{% step %}

### Symbol Placement — Prefix vs Suffix

Currency symbols are placed either before (prefix) or after (suffix) the number, depending on the market convention. The `configurationString` encodes this directly — the position of the symbol relative to the number pattern tells you which it is.

| `configurationString` pattern | Symbol placement              | Example      |
| ----------------------------- | ----------------------------- | ------------ |
| `"$ #,##0.00"`                | Prefix (symbol before number) | `$1,234.99`  |
| `"#,##0.00 €"`                | Suffix (symbol after number)  | `1,234.99 €` |
| `"¥ #,##0"`                   | Prefix, zero exponent         | `¥1,235`     |
| `"#,##0 kr"`                  | Suffix, zero exponent         | `1,235 kr`   |

When implementing programmatically, determine symbol placement from `configurationString` rather than hardcoding by currency. Some currencies (e.g. `kr` for Scandinavian currencies) are used across multiple countries with different placement conventions.
{% endstep %}

{% step %}

### Trailing Zeros

`showTrailingZeros` controls whether the fractional part is shown when it is exactly zero.

| `showTrailingZeros` | Price (integer) | Price (fractional) |
| ------------------- | --------------- | ------------------ |
| `true`              | `$100.00`       | `$100.53`          |
| `false`             | `$100`          | `$100.53`          |

Note that `showTrailingZeros` only affects the display of a price whose decimal part is exactly `.00` (or `.000` for 3-exponent currencies). It never suppresses a non-zero fractional part.

```javascript
function applyTrailingZeros(formattedFractional, showTrailingZeros, exponent) {
  if (exponent === 0) return formattedFractional;  // no decimal part possible
  if (!showTrailingZeros && formattedFractional === '0'.repeat(exponent)) {
    return null;  // signal: omit decimal part entirely
  }
  return formattedFractional;
}
```

{% endstep %}

{% step %}

### &#x20;`configurationString`

The `configurationString` is a compact format pattern that encodes everything needed to display a price: symbol, its placement, the thousand separator, the decimal separator, and the precision. It follows a spreadsheet-style number format notation.

#### Pattern Anatomy

```
"$ #,##0.00"
 │ │   │ └─ Two decimal places (.00)
 │ │   └─── Integer part with thousand grouping (#,##0)
 │ └─────── Space between symbol and number
 └───────── Symbol at the start = prefix placement
```

```
"#.##0,00 €"
 │    │   └─ Symbol at the end = suffix placement
 │    └───── Comma decimal separator
 └────────── Period thousand separator
```

#### Pattern Tokens

| Token      | Meaning                                                                                       |
| ---------- | --------------------------------------------------------------------------------------------- |
| `#`        | Optional digit placeholder (leading zeros suppressed)                                         |
| `0`        | Required digit placeholder (leading zeros shown)                                              |
| `,`        | Thousand separator (the actual character used is `thousandSeparator` from the display config) |
| `.`        | Decimal separator (the actual character used is `decimalSeparator` from the display config)   |
| `{symbol}` | The currency symbol — its position indicates prefix or suffix                                 |

#### Using `configurationString` vs Individual Fields

You have two implementation options:

**Option A — Parse `configurationString` directly** and use it as a template, substituting the actual separator characters. This is compact but requires a small parser.

**Option B — Use the individual fields** (`currencySymbol`, `decimalSeparator`, `thousandSeparator`, `currencyExponent`, `showTrailingZeros`) and build the formatted string programmatically. This is more verbose but more explicit.

Both options produce the same output. Option B is generally easier to implement correctly — the individual fields are each for one specific purpose. Option A is useful if you have an existing formatter that accepts Excel-style format strings.
{% endstep %}

{% step %}

### JavaScript Implementation

The following functions implement both rounding and display using the API's config objects.

#### Rounding Function

{% code expandable="true" %}

```javascript
/**
 * Round a raw price according to a RoundingConfigurationResponse.
 * @param {number} rawPrice
 * @param {object} roundingConfig - RoundingConfigurationResponse
 * @returns {number} - rounded price
 */
function applyRounding(rawPrice, roundingConfig) {
  if (!roundingConfig) return rawPrice;

  const exponent  = roundingConfig.currencyExponent ?? 2;
  const direction = (roundingConfig.direction ?? 'Nearest').toLowerCase();
  const factor    = Math.pow(10, exponent);

  let rounded;
  switch (direction) {
    case 'up':      rounded = Math.ceil(rawPrice  * factor) / factor; break;
    case 'down':    rounded = Math.floor(rawPrice * factor) / factor; break;
    case 'nearest':
    default:        rounded = Math.round(rawPrice * factor) / factor; break;
  }

  // Psychological model: adjust to nearest .99 (2dp) or X9 (0dp) below
  if ((roundingConfig.model ?? '').toLowerCase() === 'psychological') {
    rounded = applyPsychologicalRounding(rounded, exponent);
  }

  return rounded;
}

/**
 * Apply psychological (charm) pricing adjustment.
 * Targets .99 for 2dp currencies and the nearest X9 for 0dp currencies.
 * Confirm exact targets with your eShopWorld integration team.
 */
function applyPsychologicalRounding(roundedPrice, exponent) {
  if (exponent === 2) {
    // E.g. $100.54 → $99.99
    const floor = Math.floor(roundedPrice);
    const target = floor - 0.01;   // e.g. 99.99
    return roundedPrice > target ? target : roundedPrice;
  }
  if (exponent === 0) {
    // E.g. ¥17,584 → ¥17,499
    const magnitude = Math.pow(10, Math.floor(Math.log10(roundedPrice)));
    const target = Math.floor(roundedPrice / magnitude) * magnitude - 1;
    return roundedPrice > target ? target : roundedPrice;
  }
  return roundedPrice;  // no adjustment for other exponents
}
```

{% endcode %}

***

#### Display / Formatting Function

{% code expandable="true" %}

```javascript
/**
 * Format a rounded price for display using a CurrencyDisplayResponse.
 * @param {number} roundedPrice
 * @param {object} displayConfig - CurrencyDisplayResponse
 * @returns {string} - formatted price string ready for display
 */
function formatPrice(roundedPrice, displayConfig) {
  if (!displayConfig) return String(roundedPrice);

  const {
    currencySymbol,
    currencyExponent,
    decimalSeparator,
    thousandSeparator,
    showTrailingZeros,
    configurationString
  } = displayConfig;

  const exponent = currencyExponent ?? 2;

  // Split into integer and fractional parts
  const factor           = Math.pow(10, exponent);
  const integerPart      = Math.floor(Math.abs(roundedPrice));
  const fractionalValue  = Math.round((Math.abs(roundedPrice) - integerPart) * factor);
  const fractionalString = exponent > 0
    ? fractionalValue.toString().padStart(exponent, '0')
    : null;

  // Apply thousand separator to integer part
  const integerString = integerPart
    .toString()
    .replace(/\B(?=(\d{3})+(?!\d))/g, thousandSeparator ?? ',');

  // Build decimal part (respecting showTrailingZeros)
  let decimalPart = '';
  if (exponent > 0) {
    const isAllZero = fractionalString === '0'.repeat(exponent);
    if (!isAllZero || showTrailingZeros) {
      decimalPart = (decimalSeparator ?? '.') + fractionalString;
    }
  }

  // Assemble the numeric string
  const sign        = roundedPrice < 0 ? '-' : '';
  const numericStr  = sign + integerString + decimalPart;

  // Determine symbol placement from configurationString
  const symbolIsPrefix = isSymbolPrefix(configurationString, currencySymbol);
  const separator       = ' ';  // space between symbol and number (read from configurationString if needed)

  return symbolIsPrefix
    ? `${currencySymbol}${separator}${numericStr}`.trimStart()
    : `${numericStr}${separator}${currencySymbol}`.trimEnd();
}

/**
 * Determine whether the currency symbol is a prefix or suffix
 * by checking its position in the configurationString.
 */
function isSymbolPrefix(configurationString, currencySymbol) {
  if (!configurationString || !currencySymbol) return true;  // default to prefix
  const symbolIndex  = configurationString.indexOf(currencySymbol);
  const patternIndex = configurationString.search(/[#0]/);
  return symbolIndex < patternIndex;
}
```

{% endcode %}

***

#### Putting It All Together

{% code expandable="true" %}

```javascript
/**
 * Full pipeline: raw price → rounded → formatted string.
 */
function roundAndFormat(rawPrice, category, destinationCurrencyIso) {
  // Select configs for this currency
  const roundingConfig = category.roundingConfigurations
    ?.find(r => r.currencyIso === destinationCurrencyIso);
  const displayConfig = category.currencyDisplays
    ?.find(d => d.currencyIso === destinationCurrencyIso);

  if (!roundingConfig) {
    console.warn(`No rounding config for ${destinationCurrencyIso} in category ${category.id}`);
  }
  if (!displayConfig) {
    console.warn(`No display config for ${destinationCurrencyIso} in category ${category.id}`);
  }

  const rounded   = applyRounding(rawPrice, roundingConfig);
  const formatted = formatPrice(rounded, displayConfig);

  return { rounded, formatted };
}


// ── Example usage ────────────────────────────────────────────────────────────

const usCategory = {
  id: 'ApparelAccessories',
  roundingConfigurations: [
    { currencyIso: 'USD', currencyExponent: 2, direction: 'Up', model: 'Standard' }
  ],
  currencyDisplays: [
    {
      currencyIso: 'USD', currencySymbol: '$', currencyExponent: 2,
      decimalSeparator: '.', thousandSeparator: ',',
      showTrailingZeros: true, configurationString: '$ #,##0.00'
    }
  ]
};

const jpCategory = {
  id: 'ApparelAccessories',
  roundingConfigurations: [
    { currencyIso: 'JPY', currencyExponent: 0, direction: 'Up', model: 'Standard' }
  ],
  currencyDisplays: [
    {
      currencyIso: 'JPY', currencySymbol: '¥', currencyExponent: 0,
      decimalSeparator: '.', thousandSeparator: ',',
      showTrailingZeros: false, configurationString: '¥ #,##0'
    }
  ]
};

const deCategory = {
  id: 'ApparelAccessories',
  roundingConfigurations: [
    { currencyIso: 'EUR', currencyExponent: 2, direction: 'Up', model: 'Standard' }
  ],
  currencyDisplays: [
    {
      currencyIso: 'EUR', currencySymbol: '€', currencyExponent: 2,
      decimalSeparator: ',', thousandSeparator: '.',
      showTrailingZeros: true, configurationString: '#.##0,00 €'
    }
  ]
};

console.log(roundAndFormat(100.534,   usCategory, 'USD'));
// { rounded: 100.54,  formatted: '$ 100.54'    }  →  displays as: $100.54

console.log(roundAndFormat(17583.51,  jpCategory, 'JPY'));
// { rounded: 17584,   formatted: '¥ 17,584'    }  →  displays as: ¥17,584

console.log(roundAndFormat(1234.001,  deCategory, 'EUR'));
// { rounded: 1234.01, formatted: '1.234,01 €'  }  →  displays as: 1.234,01 €

console.log(roundAndFormat(100.0,     usCategory, 'USD'));
// { rounded: 100.00,  formatted: '$ 100.00'    }  →  showTrailingZeros: true

console.log(roundAndFormat(100.0,     jpCategory, 'JPY'));
// { rounded: 100,     formatted: '¥ 100'       }  →  zero exponent, no decimal
```

{% endcode %}
{% endstep %}
{% endstepper %}

### Summary — Two-Step Display Pipeline

{% code expandable="true" %}

```ruby
rawPrice (floating-point result of calculation)
    │
    ▼
applyRounding(rawPrice, roundingConfig)
    │  → select roundingConfig where currencyIso === destinationCurrency
    │  → apply direction (Up/Down/Nearest) at currencyExponent precision
    │  → apply model (Standard/Psychological)
    │
    ▼
roundedPrice (clean number at correct precision)
    │
    ▼
formatPrice(roundedPrice, displayConfig)
    │  → select displayConfig where currencyIso === destinationCurrency
    │  → apply thousandSeparator to integer part
    │  → apply decimalSeparator + fractional digits
    │  → apply showTrailingZeros
    │  → place currencySymbol (prefix or suffix per configurationString)
    │
    ▼
displayString (ready for UI)
```

{% endcode %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://developer.esw.com/pricing-advisor-api/resources/tutorials/currency-display-and-rounding.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
