Cart normalizer
The normalizeCart
function is used to map a Commercetools Cart into the unified SfCart
data model.
Parameters
Name | Type | Default value | Description |
---|---|---|---|
cart | Cart | Commercetools Cart | |
ctx | NormalizerContext | context needed for the normalizer |
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 a version
field.
import { normalizers as normalizersCT, defineNormalizers } from "@vsf-enterprise/unified-api-commercetools";
const normalizers = defineNormalizers<typeof normalizersCT>()({
...normalizersCT,
normalizeCart: (cart, context) => ({
...normalizersCT.normalizeCart(cart, context),
version: cart.version,
}),
});
You can override the normalizeCart
, but it's also available to override the smaller normalizers such as normalizeCartCoupon
, normalizeShippingMethod
, normalizeCartLineItem
.
Source
The normalizeCart
function consists of several smaller normalizers such as normalizeCartCoupon
, normalizeShippingMethod
, and more.
cart.ts
import type { NormalizerContext } from "@/normalizers/types";
import { maybe } from "@shared/utils";
import type { Cart } from "@vsf-enterprise/commercetools-types";
import type { SfCart } from "@vue-storefront/unified-data-model";
import { defineNormalizer } from "../defineNormalizer";
export const normalizeCart = defineNormalizer.normalizeCart((cart, ctx) => {
const { lineItems } = normalizeLineItems(cart, ctx);
const { appliedCoupons } = normalizeDiscounts(cart, ctx);
const { shippingMethod, totalShippingPrice } = normalizeShipping(cart, ctx);
const { billingAddress, shippingAddress } = normalizeAddresses(cart, ctx);
const {
totalItems,
totalPrice,
totalTax,
subtotalRegularPrice,
subtotalDiscountedPrice,
totalCouponDiscounts,
} = normalizeTotals(cart, ctx);
return {
id: cart.id,
customerEmail: getCartCustomerEmail(cart),
lineItems,
totalPrice,
subtotalRegularPrice,
subtotalDiscountedPrice,
totalItems,
appliedCoupons,
billingAddress,
shippingAddress,
shippingMethod,
totalShippingPrice,
totalCouponDiscounts,
totalTax,
};
});
function normalizeDiscounts(cart: Cart, ctx: NormalizerContext): Pick<SfCart, "appliedCoupons"> {
const appliedCoupons = cart.discountCodes
.map((discountCode) =>
discountCode.discountCode
? ctx.normalizers.normalizeCartCoupon(discountCode.discountCode)
: null,
)
.filter((item) => item !== null) as SfCart["appliedCoupons"];
return {
appliedCoupons,
};
}
function normalizeShipping(
cart: Cart,
ctx: NormalizerContext,
): Pick<SfCart, "shippingMethod" | "totalShippingPrice"> {
const { normalizeMoney, normalizeShippingMethod } = ctx.normalizers;
const shippingMethod =
cart.shippingInfo?.shippingMethod &&
normalizeShippingMethod({
...cart.shippingInfo.shippingMethod,
totalPrice: cart.totalPrice,
});
const totalShippingPrice = cart.shippingInfo && normalizeMoney(cart.shippingInfo.price);
return {
shippingMethod: maybe(shippingMethod),
totalShippingPrice: maybe(totalShippingPrice),
};
}
function normalizeAddresses(
cart: Cart,
ctx: NormalizerContext,
): Pick<SfCart, "billingAddress" | "shippingAddress"> {
const { normalizeAddress } = ctx.normalizers;
return {
billingAddress: cart.billingAddress ? normalizeAddress(cart.billingAddress) : null,
shippingAddress: cart.shippingAddress ? normalizeAddress(cart.shippingAddress) : null,
};
}
type Totals = Pick<
SfCart,
| "totalPrice"
| "subtotalRegularPrice"
| "subtotalDiscountedPrice"
| "totalItems"
| "totalTax"
| "totalCouponDiscounts"
>;
function normalizeTotals(cart: Cart, ctx: NormalizerContext): Totals {
const { normalizeMoney } = ctx.normalizers;
const totalPrice = normalizeMoney(cart.totalPrice);
const totalItems = cart.lineItems.reduce((total, item) => total + item.quantity, 0);
const { regular: subtotalRegularCentAmount, discounted: subtotalDiscountedCentAmount } =
cart.lineItems.reduce(
(total, item) => {
const regular = item.price.value.centAmount * item.quantity;
total.regular += regular;
total.discounted += item.price.discounted?.value?.centAmount * item.quantity || regular;
return total;
},
{ regular: 0, discounted: 0 },
);
const subtotalRegularPrice = normalizeMoney({
...cart.totalPrice,
centAmount: subtotalRegularCentAmount,
});
const subtotalDiscountedPrice = normalizeMoney({
...cart.totalPrice,
centAmount: subtotalDiscountedCentAmount,
});
const totalTax =
cart.taxedPrice?.totalGross && cart.taxedPrice?.totalNet
? normalizeMoney({
...cart.taxedPrice.totalGross,
centAmount: cart.taxedPrice.totalGross.centAmount - cart.taxedPrice.totalNet.centAmount,
})
: normalizeMoney({
...cart.totalPrice,
centAmount: 0,
});
const totalCouponDiscounts = normalizeTotalDiscounts(cart, ctx);
return {
totalPrice,
totalItems,
totalTax,
subtotalRegularPrice,
subtotalDiscountedPrice,
totalCouponDiscounts,
};
}
function normalizeLineItems(cart: Cart, ctx: NormalizerContext): Pick<SfCart, "lineItems"> {
return {
lineItems: cart.lineItems
.map((lineItem) => ctx.normalizers.normalizeCartLineItem(lineItem))
.filter(Boolean),
};
}
function normalizeTotalDiscounts(cart: Cart, ctx: NormalizerContext) {
const totalCouponsAmount = calculateDiscountsValue(cart);
return ctx.normalizers.normalizeMoney({
centAmount: totalCouponsAmount ?? 0,
fractionDigits: cart.totalPrice.fractionDigits,
currencyCode: cart.totalPrice.currencyCode,
type: cart.totalPrice.type,
});
}
function calculateDiscountsValue(cart: Cart) {
const { lineItems } = cart;
if (!lineItems || lineItems.length <= 0) {
return null;
}
return lineItems.reduce((totalAmount, lineItem) => {
const lineItemDiscountAmount = lineItem.discountedPricePerQuantity.reduce(
(amount, discountedPricePerQuantity) => {
const original =
(lineItem.price.discounted?.value.centAmount || lineItem.price.value.centAmount) *
discountedPricePerQuantity.quantity;
const discounted =
discountedPricePerQuantity.discountedPrice.value.centAmount *
discountedPricePerQuantity.quantity;
return amount + (original - discounted);
},
0,
);
return totalAmount + lineItemDiscountAmount;
}, 0);
}
function getCartCustomerEmail(cart: Cart): string | null {
return cart.customerEmail || cart.customer?.email || null;
}