> 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/checkout-api/checkout-api/error-handling/validation-and-special-field-behaviours.md).

# Validation and Special Field Behaviours

### Pre-Flight Validation Function

{% code expandable="true" %}

```javascript
function validateOrderConfirmation(payload) {
  const errors = [];

  const require = (val, field) => {
    if (val === null || val === undefined || val === "") {
      errors.push(`${field} is required.`);
    }
  };
  const maxLen = (val, field, max) => {
    if (val && val.length > max) errors.push(`${field} exceeds max length of ${max}.`);
  };
  const exactLen = (val, field, len) => {
    if (val !== null && val !== undefined && val.length !== len) {
      errors.push(`${field} must be exactly ${len} characters (received: "${val}").`);
    }
  };
  const validEnum = (val, field, allowed) => {
    if (val !== null && val !== undefined && !allowed.includes(val)) {
      errors.push(`${field} must be one of: ${allowed.join(", ")} (received: "${val}").`);
    }
  };

  // Top-level
  require(payload?.retailerCartId, "retailerCartId");
  maxLen(payload?.retailerCartId, "retailerCartId", 100);
  require(payload?.eShopWorldOrderNumber, "eShopWorldOrderNumber");
  maxLen(payload?.eShopWorldOrderNumber, "eShopWorldOrderNumber", 36);
  require(payload?.deliveryCountryIso, "deliveryCountryIso");
  exactLen(payload?.deliveryCountryIso, "deliveryCountryIso", 2);
  validEnum(payload?.cartType, "cartType", ["Standard", "ZeroValue"]);

  // checkoutTotal
  require(payload?.checkoutTotal?.retailer?.currency, "checkoutTotal.retailer.currency");
  exactLen(payload?.checkoutTotal?.retailer?.currency, "checkoutTotal.retailer.currency", 3);
  require(payload?.checkoutTotal?.retailer?.amount, "checkoutTotal.retailer.amount");
  require(payload?.checkoutTotal?.shopper?.currency, "checkoutTotal.shopper.currency");
  exactLen(payload?.checkoutTotal?.shopper?.currency, "checkoutTotal.shopper.currency", 3);
  require(payload?.checkoutTotal?.shopper?.amount, "checkoutTotal.shopper.amount");

  // paymentDetails
  require(payload?.paymentDetails?.method, "paymentDetails.method");
  require(payload?.paymentDetails?.time, "paymentDetails.time");

  // contactDetails
  if (!payload?.contactDetails?.length) {
    errors.push("contactDetails must contain at least one entry.");
  }
  (payload?.contactDetails ?? []).forEach((cd, i) => {
    require(cd?.contactDetailType, `contactDetails[${i}].contactDetailType`);
    validEnum(cd?.contactDetailType, `contactDetails[${i}].contactDetailType`, ["IsDelivery", "IsPayment"]);
    require(cd?.country, `contactDetails[${i}].country`);
    exactLen(cd?.country, `contactDetails[${i}].country`, 2);
  });

  // lineItems
  if (!payload?.lineItems?.length) {
    errors.push("lineItems must contain at least one item.");
  }
  (payload?.lineItems ?? []).forEach((li, i) => {
    require(li?.product?.productCode, `lineItems[${i}].product.productCode`);
    maxLen(li?.product?.productCode, `lineItems[${i}].product.productCode`, 100);
    require(li?.product?.hsCode, `lineItems[${i}].product.hsCode`);
    maxLen(li?.product?.hsCode, `lineItems[${i}].product.hsCode`, 12);
    require(li?.product?.title, `lineItems[${i}].product.title`);
    maxLen(li?.product?.title, `lineItems[${i}].product.title`, 100);
    require(li?.product?.description, `lineItems[${i}].product.description`);
    require(li?.charges?.subTotal, `lineItems[${i}].charges.subTotal`);
    require(li?.estimatedDeliveryDate, `lineItems[${i}].estimatedDeliveryDate`);
  });

  // charges (order-level)
  require(payload?.charges?.total, "charges.total");

  if (errors.length > 0) {
    throw new ValidationError("Order confirmation payload failed validation", errors);
  }
}
```

{% endcode %}

***

## Special Field Behaviours

### `amount` Is Always a String

Every monetary amount in this API is typed as `string`, not `number`. This applies to all instances of `OrderConfirmationMoneyRequestDto`.

```javascript
// WRONG — will cause a 400
{ "currency": "GBP", "amount": 149.99 }

// CORRECT
{ "currency": "GBP", "amount": "149.99" }
```

Ensure your serializer does not coerce decimal strings to numbers. In JavaScript, always explicitly convert:

```javascript
const formatAmount = (value) => parseFloat(value).toFixed(2);
// "149.9" → "149.90"  |  149.99 → "149.99"
```

### `cartType` and Zero-Value Orders

When `cartType` is `"ZeroValue"`, the `zeroValueOrderCharges` object becomes relevant. Populate it with the zero-value charge breakdown (`zeroValueOrderSubTotal`, `zeroValueOrderTaxes`, `zeroValueOrderOtherTaxes`, `zeroValueOrderDuty`). For standard orders, this object can be omitted.

### `metadataItems` Require ESW Agreement

`metadataItems` appears at multiple levels: order, line item, product, contact details, delivery option, and checkout experience. **`metadataItems` must be agreed with ESW** before sending.

```javascript
// Name max 64 chars, value max 1000 chars — validate before sending
const validateMetadata = (items, context) => {
  (items ?? []).forEach((item, i) => {
    if (!item.name) throw new Error(`${context}.metadataItems[${i}].name is required.`);
    if (item.name.length > 64) throw new Error(`${context}.metadataItems[${i}].name exceeds 64 chars.`);
    if (item.value && item.value.length > 1000) throw new Error(`${context}.metadataItems[${i}].value exceeds 1000 chars.`);
  });
};
```

### `discountInfo.percentage` vs `discount`/`beforeDiscount`

When applying a discount via `OrderConfirmationDiscountInfoRequestDto`, you can use either the percentage-based approach or the explicit amount approach — not both:

* **Percentage approach:** Populate `percentage` (a string like `"25"`, between 0 and 100 exclusive). Leave `discount` and `beforeDiscount` absent.
* **Amount approach:** Populate both `discount` (`OrderConfirmationAmountRequestDto`) and `beforeDiscount` (`OrderConfirmationAmountRequestDto`). Leave `percentage` absent.

```javascript
// Percentage approach
{ "title": "Season Sale", "percentage": "25" }

// Amount approach
{
  "title": "Season Sale",
  "discount": { "retailer": { "currency": "GBP", "amount": "37.50" }, "shopper": { ... } },
  "beforeDiscount": { "retailer": { "currency": "GBP", "amount": "149.99" }, "shopper": { ... } }
}
```

### `deliveryOption.deliveryOption` — 15 Character Hard Limit

The delivery option code has a hard maximum of 15 characters and must match a code pre-agreed with ESW. Do not construct this string dynamically — use a constant from your ESW configuration:

```javascript
const DELIVERY_OPTIONS = {
  STANDARD: "STD",
  EXPRESS: "EXP",
  NEXT_DAY: "NXT",
  // ... add as agreed with ESW
};
```

### Multiple `contactDetails` Entries

When passing multiple contact detail objects, they are applied first-to-last. If you send both a delivery and a payment address, use `contactDetailType: "IsDelivery"` and `contactDetailType: "IsPayment"` respectively. The array must contain at least one entry.


---

# 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/checkout-api/checkout-api/error-handling/validation-and-special-field-behaviours.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.
