The Vue Storefront Essentials Course is now available! Learn More
Cart normalizer

Cart normalizer

The normalizeCart function is used to map a SAP Cart into the unified SfCart data model.

Parameters

NameTypeDefault valueDescription
cartCartSAP Cart
ctxNormalizeCartContextcontext needed for the normalizer. transformImageUrl is added to transform line items image urls

Extending

The SfCart structure is returned from all Unified Cart Methods such as GetCart, AddCartLineItem, and UpdateCartLineItem. If the SfCart structure doesn't contain the information you need for your Storefront, you can extend its logic using the defineNormalizers function. The following example demonstrates how to extend SfCart with an expirationTime field.

import { normalizers as normalizersSAP, defineNormalizers } from "@vsf-enterprise/unified-api-sapcc";

const normalizers = defineNormalizers<typeof normalizersSAP>()({
  ...normalizersSAP,
  normalizeCart: (cart, context) => ({
    ...normalizersSAP.normalizeCart(cart, context),
    expirationTime: cart.expirationTime,
  }),
});

Source

The normalizeCart function consists of several smaller normalizers such as normalizeCartCoupon, normalizeShippingMethod, and more. If you decide to override the normalization function, you should only override the root normalizer, which is normalizeCart.

cart.ts
import type { Address, Cart, Price } from "@vsf-enterprise/sapcc-types";
import type { NormalizerContext } from "@/normalizers/types";
import {
  Maybe,
  SfAddress,
  SfCart,
  SfMoney,
  SfShippingMethod,
} from "@vue-storefront/unified-data-model";
import { normalizeMoney } from "../money";
import { normalizeAddress } from "./address";
import { normalizeShippingMethod } from "./shippingMethod";
import { normalizeCartCoupon } from "./cartCoupon";
import { normalizeCartLineItem } from "./lineItem";

export function normalizeCart(cart: Cart, ctx: NormalizerContext): SfCart {
  const appliedCoupons = cart.appliedVouchers?.map((voucher) => normalizeCartCoupon(voucher)) ?? [];
  const billingAddress = getAddress(cart.paymentInfo?.billingAddress);
  const customerEmail = getCustomerEmail(cart);
  const lineItems = cart.entries?.map((entry) => normalizeCartLineItem(entry, ctx)) ?? [];
  const shippingAddress = getAddress(cart.deliveryAddress);
  const shippingMethod = getShippingMethod(cart);
  const subtotalDiscountedPrice = getSubtotalDiscountedPrice(cart);
  const totalShippingPrice = getTotalShippingPrice(cart);
  const id = (ctx.isAuthenthicated ? cart.code : cart.guid) as string;

  return {
    appliedCoupons,
    billingAddress,
    customerEmail,
    id,
    lineItems,
    shippingAddress,
    shippingMethod,
    subtotalDiscountedPrice,
    subtotalRegularPrice: getSubtotalRegularPrice(cart),
    totalCouponDiscounts: normalizeMoney(cart.orderDiscounts as Price),
    totalItems: cart.totalUnitCount as number,
    totalPrice: normalizeMoney(cart.totalPriceWithTax as Price),
    totalShippingPrice,
    totalTax: normalizeMoney(cart.totalTax as Price),
  };
}

function getAddress(address: Address | undefined): Maybe<SfAddress> {
  return address ? normalizeAddress(address) : null;
}

function getCustomerEmail(cart: Cart): SfCart["customerEmail"] {
  // example uid: 42bc98b7-63ab-407b-9864-007acfa50888|email@example.com or just email
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  const uidParts = cart.user?.uid?.split("|") ?? [];
  return uidParts.find((element) => /^\S+@\S+\.\S+?$/.test(element)) ?? null;
}

function getShippingMethod(cart: Cart): Maybe<SfShippingMethod> {
  return cart.deliveryMode ? normalizeShippingMethod(cart.deliveryMode) : null;
}

function getTotalShippingPrice(cart: Cart): Maybe<SfMoney> {
  return cart.deliveryCost ? normalizeMoney(cart.deliveryCost) : null;
}

/**
 * Calculates cart subtotal price after item discounts are applied, before order discounts, shipping and tax.
 */
function getSubtotalDiscountedPrice(cart: Cart): SfMoney {
  const productsPriceAfterDiscounts =
    cart.entries?.reduce((acc, entry) => {
      return acc + (entry.totalPrice?.value ?? 0);
    }, 0) ?? 0;
  return normalizeMoney({
    currencyIso: cart.totalPrice?.currencyIso,
    value: productsPriceAfterDiscounts,
  });
}

/**
 * Calculates cart regular subtotal price before item discounts, order discounts, shipping and tax.
 *
 * Note: `subTotal` field in SAPCC represents the price **AFTER** item and order discounts are applied.
 */
function getSubtotalRegularPrice(cart: Cart): SfMoney {
  const productsPriceBeforeDiscounts =
    cart.entries?.reduce((acc, { quantity = 1, basePrice }) => {
      return acc + (basePrice?.value ?? 0) * quantity;
    }, 0) ?? 0;

  return normalizeMoney({
    currencyIso: cart.totalPrice?.currencyIso,
    value: productsPriceBeforeDiscounts,
  });
}