Alokai
ReferenceSAP Commerce CloudNormalizers

Order normalizer

  • normalizeOrder: This function is used to map SAP Order into SfOrder, which includes order details data.
  • normalizeOrderListItem: This function maps SAP OrderHistory into Unified SfOrderListItem which includes only basic order details, used to display data in an order list.

Parameters

normalizeOrder

NameTypeDefault valueDescription
contextNormalizerContextcontext needed for the normalizer.
inputOrderSAP Order

normalizeOrderListItem

NameTypeDefault valueDescription
contextNormalizerContextcontext needed for the normalizer.
inputOrderHistorySAP Order List Element

normalizeOrderLineItem

NameTypeDefault valueDescription
contextNormalizerContextcontext needed for the normalizer.
inputOrderEntrySAP Order Entry

Extending

The SfOrder is returned from the GetOrders Method. If the SfOrder structure doesn't contain the information you need for your Storefront, you can extend its logic using the addCustomFields API. The following example demonstrates how to extend SfOrder with an orderDiscounts field.

export const unifiedApiExtension = createUnifiedExtension({
  normalizers: {
    addCustomFields: [
      {
        normalizeOrder: (context, order) => ({
          orderDiscounts: order.orderDiscounts,
        }),
      },
    ],
  },
  config: {
    ...
  },
});

You can override the normalizeOrder, but it's also available to override the smaller normalizers such as normalizeAddress, normalizeShippingMethod.

Source

import { ValidationError } from "@alokai/connect/middleware";
import { z } from "zod";

import { defineNormalizer } from "../defineNormalizer";

const orderSchema = z.looseObject({
code: z.string(),
created: z.string(),
deliveryAddress: z.looseObject({}),
deliveryCost: z.looseObject({}),
deliveryMode: z.looseObject({}),
entries: z.array(z.any()),
subTotal: z.looseObject({}),
totalPriceWithTax: z.looseObject({}),
totalTax: z.looseObject({}),
});

export const normalizeOrder = defineNormalizer.normalizeOrder((context, input) => {
const result = orderSchema.safeParse(input);
if (!result.success) {
  throw ValidationError.fromStandardSchemaError(result.error, "Missing required order fields");
}

const {
  code,
  costCenter,
  created,
  deliveryAddress,
  deliveryCost,
  deliveryMode,
  entries,
  paymentInfo,
  status,
  subTotal,
  totalPriceWithTax,
  totalTax,
} = result.data as typeof input & typeof result.data;

const { normalizeAddress, normalizeMoney, normalizeOrderLineItem, normalizeShippingMethod } =
  context.normalizers;
const subTotalWithoutTax = {
  ...subTotal!,
  value: subTotal!.value! - totalTax!.value!,
};
const billingAddress = paymentInfo?.billingAddress
  ? context.normalizers.normalizeAddress(paymentInfo.billingAddress)
  : null;
const paymentMethod = costCenter ? "ACCOUNT" : "CARD";

return {
  billingAddress,
  id: code!,
  lineItems: entries!.map((entry) => normalizeOrderLineItem(entry)),
  orderDate: new Date(created!).toISOString(),
  paymentMethod,
  shippingAddress: normalizeAddress(deliveryAddress!),
  shippingMethod: normalizeShippingMethod(deliveryMode!),
  status: status ?? "UNKNOWN",
  subtotalPrice: normalizeMoney(subTotalWithoutTax),
  totalPrice: normalizeMoney(totalPriceWithTax!),
  totalShippingPrice: normalizeMoney(deliveryCost!),
  totalTax: normalizeMoney(totalTax!),
};
});
import { ValidationError } from "@alokai/connect/middleware";
import { z } from "zod";

import { defineNormalizer } from "../defineNormalizer";

const orderHistorySchema = z.looseObject({
code: z.string(),
placed: z.string(),
status: z.string().optional(),
total: z.looseObject({}),
});

export const normalizeOrderListItem = defineNormalizer.normalizeOrderListItem((context, input) => {
const result = orderHistorySchema.safeParse(input);
if (!result.success) {
  throw ValidationError.fromStandardSchemaError(
    result.error,
    "OrderHistory is missing required fields",
  );
}

const { code, placed, status, total } = result.data as typeof input & typeof result.data;

return {
  id: code,
  orderDate: new Date(placed).toISOString(),
  status: status ?? "unknown",
  totalPrice: context.normalizers.normalizeMoney(total),
};
});
import { ValidationError } from "@alokai/connect/middleware";
import type { VariantOptionQualifier } from "@vsf-enterprise/sapcc-types";
import { z } from "zod";

import { maybe } from "@vsf-enterprise/unified-api-sapcc";
import { getOptions } from "@vsf-enterprise/unified-api-sapcc";
import type { NormalizerContext } from "@vsf-enterprise/unified-api-sapcc";

import { defineNormalizer } from "../defineNormalizer";
import { createSfImages } from "../product/images";

const orderEntrySchema = z.looseObject({
basePrice: z.looseObject({}),
entryNumber: z.number().optional(),
product: z.looseObject({}),
quantity: z.number().optional(),
totalPrice: z.looseObject({}),
});

export const normalizeOrderLineItem = defineNormalizer.normalizeOrderLineItem((context, input) => {
const result = orderEntrySchema.safeParse(input);
if (!result.success) {
  throw ValidationError.fromStandardSchemaError(
    result.error,
    "OrderEntry is missing required fields",
  );
}

const { basePrice, entryNumber, product, quantity, totalPrice } = result.data as typeof input &
  typeof result.data;

const { currentOption } = getOptions(product!, product!.code as string);
const attributes = getAttributes(context, currentOption?.variantOptionQualifiers ?? []);
const { primaryImage } = createSfImages(context, product.images);

return {
  attributes,
  id: entryNumber!.toString(),
  image: maybe(primaryImage),
  productId: product.code ?? "",
  productName: product.name ?? "",
  quantity: quantity ?? 1,
  sku: maybe(product.code),
  totalPrice: context.normalizers.normalizeMoney(totalPrice),
  unitPrice: context.normalizers.normalizeMoney(basePrice),
};
});

function getAttributes(context: NormalizerContext, optionQualifiers: VariantOptionQualifier[]) {
return optionQualifiers
  .map((optionQualifier) => context.normalizers.normalizeAttribute(optionQualifier))
  .filter(Boolean);
}

On this page