UNPKG

@scayle/storefront-core

Version:

Collection of essential utilities to work with the Storefront API

432 lines (431 loc) 13.6 kB
import { ExistingItemHandling } from "@scayle/storefront-api"; import { ErrorResponse } from "../../../errors/index.mjs"; import { hasSession } from "../../../types/index.mjs"; import { HttpStatusCode, HttpStatusMessage, MIN_WITH_PARAMS_BASKET } from "../../../constants/index.mjs"; import { mergeBaskets as mergeBasketFunction, getValidatedAccessToken } from "../../../utils/user.mjs"; import { unwrap } from "../../../utils/response.mjs"; import { defineRpcHandler, mergeOrderCustomData, wasAddedWithReducedQuantity } from "../../../utils/index.mjs"; import { mapSAPIFetchErrorToResponse } from "../../../utils/sapi.mjs"; const SAPI_ERROR_NAME = "SAPI ERROR"; function getWithParams(params, context) { return params.with ?? context.withParams?.basket ?? MIN_WITH_PARAMS_BASKET; } export const addItemToBasket = defineRpcHandler( async ({ variantId, promotionId, promotions, quantity, displayData, customData, existingItemHandling = ExistingItemHandling.ADD_QUANTITY_TO_EXISTING, itemGroup, with: _with, orderCustomData }, context) => { if (!hasSession(context)) { return new ErrorResponse( HttpStatusCode.BAD_REQUEST, HttpStatusMessage.BAD_REQUEST, "No Session found" ); } const { sapiClient, basketKey } = context; const campaignKey = await context.callRpc?.("getCampaignKey"); const _orderCustomData = await context.callRpc?.("getOrderCustomData"); const resolvedWith = getWithParams( { with: _with }, context ); const result = await sapiClient.basket.addOrUpdateItems( basketKey, [ { variantId, quantity, params: { promotionId: promotionId ?? void 0, promotions: promotions ?? void 0, campaignKey, displayData, pricePromotionKey: resolvedWith?.pricePromotionKey || "", customData, itemGroup, with: resolvedWith, includeItemsWithoutProductData: resolvedWith?.includeItemsWithoutProductData } } ], { skipAvailabilityCheck: false, orderCustomData: mergeOrderCustomData( _orderCustomData, orderCustomData ), customerToken: getValidatedAccessToken(context.accessToken) ?? void 0 }, { existingItemHandling } ); if (result.type === "success") { return { basket: result.basket }; } else if (result.type === "failure" && wasAddedWithReducedQuantity(result.errors)) { return { basket: result.basket, errors: result.errors }; } else { const { message, statusCode } = parseBasketError(result); context.log.error("Adding item to basket failed", { message, statusCode }); return new ErrorResponse( HttpStatusCode.BAD_REQUEST, SAPI_ERROR_NAME, "Adding item to basket failed", { detail: message, errors: result.errors } ); } }, { method: "PUT" } ); export const addItemsToBasket = defineRpcHandler( async (params, context) => { if (!hasSession(context)) { return new ErrorResponse( HttpStatusCode.BAD_REQUEST, HttpStatusMessage.BAD_REQUEST, "No Session found" ); } const { sapiClient, basketKey } = context; const campaignKey = await context.callRpc?.("getCampaignKey"); const _orderCustomData = await context.callRpc?.("getOrderCustomData"); const resolvedWith = getWithParams(params, context); const itemsToBeAddedOrUpdated = params.items.map((item) => ({ variantId: item.variantId, quantity: item.quantity, params: { promotionId: item.promotionId ?? void 0, promotions: item.promotions ?? void 0, campaignKey, displayData: item.displayData, pricePromotionKey: resolvedWith?.pricePromotionKey || "", customData: item.customData, with: resolvedWith, includeItemsWithoutProductData: item.includeItemsWithoutProductData ?? resolvedWith?.includeItemsWithoutProductData, itemGroup: item.itemGroup ?? void 0 } })); const result = await sapiClient.basket.addOrUpdateItems( basketKey, itemsToBeAddedOrUpdated, { skipAvailabilityCheck: false, includeItemsWithoutProductData: resolvedWith?.includeItemsWithoutProductData, orderCustomData: mergeOrderCustomData( _orderCustomData, params.orderCustomData ), customerToken: getValidatedAccessToken(context.accessToken) ?? void 0 }, { existingItemHandling: params.existingItemHandling || ExistingItemHandling.ADD_QUANTITY_TO_EXISTING } ); if (result.type === "success") { return { basket: result.basket }; } else if (result.type === "failure" && wasAddedWithReducedQuantity(result.errors)) { return { basket: result.basket, errors: result.errors }; } else { const { statusCode, message } = parseBasketError(result); context.log.error("Adding one or more items to basket failed", { statusCode, message }); return new ErrorResponse( HttpStatusCode.BAD_REQUEST, SAPI_ERROR_NAME, "Adding one or more items to basket failed", { detail: message, errors: result.errors } ); } }, { method: "PUT" } ); export const getBasket = defineRpcHandler( async (options, context) => { if (!hasSession(context)) { return new ErrorResponse( HttpStatusCode.BAD_REQUEST, HttpStatusMessage.BAD_REQUEST, "No Session found" ); } const { sapiClient, basketKey } = context; const campaignKey = await context.callRpc?.("getCampaignKey"); const _orderCustomData = await context.callRpc?.("getOrderCustomData"); const resolvedWith = getWithParams( { with: options }, context ); const response = await sapiClient.basket.get(basketKey, { with: resolvedWith, campaignKey, includeItemsWithoutProductData: resolvedWith.includeItemsWithoutProductData, orderCustomData: mergeOrderCustomData( _orderCustomData, options?.orderCustomData ), customerToken: getValidatedAccessToken(context.accessToken) ?? void 0 }); if (response.type === "failure") { const { statusCode, message } = parseBasketError(response); return new ErrorResponse( statusCode, SAPI_ERROR_NAME, "Adding item to basket failed", { detail: message } ); } return { basket: response.basket }; }, { method: "POST" } ); export const removeItemFromBasket = defineRpcHandler( async (options, context) => { if (!hasSession(context)) { return new ErrorResponse( HttpStatusCode.BAD_REQUEST, HttpStatusMessage.BAD_REQUEST, "No Session found" ); } const { sapiClient, basketKey } = context; const campaignKey = await context.callRpc?.("getCampaignKey"); const _orderCustomData = await context.callRpc?.("getOrderCustomData"); const resolvedWith = getWithParams(options, context); const response = await sapiClient.basket.deleteItem( basketKey, options.itemKey, { with: resolvedWith, campaignKey, includeItemsWithoutProductData: resolvedWith?.includeItemsWithoutProductData, orderCustomData: mergeOrderCustomData( _orderCustomData, options.orderCustomData ), customerToken: getValidatedAccessToken(context.accessToken) ?? void 0 } ); if ("code" in response) { return new ErrorResponse(response.code, SAPI_ERROR_NAME, response.message); } return { basket: response }; }, { method: "DELETE" } ); export const clearBasket = defineRpcHandler( async (context) => { const getBasketResponse = await getBasket({}, context); if (getBasketResponse instanceof ErrorResponse) { return getBasketResponse; } const { basket } = await unwrap(getBasketResponse); for (const item of basket.items) { const response = await removeItemFromBasket( { itemKey: item.key }, context ); if (response instanceof ErrorResponse) { return response; } } return true; }, { method: "DELETE" } ); export const mergeBaskets = defineRpcHandler( async ({ fromBasketKey, toBasketKey, with: _with, orderCustomData }, context) => { const resolvedWith = getWithParams( { with: _with }, context ); const _orderCustomData = await context.callRpc?.("getOrderCustomData"); return await mergeBasketFunction( fromBasketKey, toBasketKey, { ...resolvedWith, orderCustomData: mergeOrderCustomData( _orderCustomData, orderCustomData ) }, context ); }, { method: "POST" } ); export const updateBasketItem = defineRpcHandler( async ({ basketItemKey, update, with: _with, orderCustomData }, context) => { if (!hasSession(context)) { return new ErrorResponse( HttpStatusCode.BAD_REQUEST, HttpStatusMessage.BAD_REQUEST, "No Session found" ); } const { basketKey, sapiClient } = context; const resolvedWith = getWithParams({ with: _with }, context); const { quantity, ...updateParams } = update; const campaignKey = update.campaignKey ?? await context.callRpc?.("getCampaignKey"); const _orderCustomData = await context.callRpc?.("getOrderCustomData"); const result = await sapiClient.basket.updateItem( basketKey, basketItemKey, quantity, { ...updateParams, campaignKey, with: resolvedWith, orderCustomData: mergeOrderCustomData( _orderCustomData, orderCustomData ), customerToken: getValidatedAccessToken(context.accessToken) ?? void 0 } ); if (result.type === "success") { return { basket: result.basket }; } else { context.log.error("Updating basket item failed", { kind: result.kind, basketKey, basketItemKey, update }); return new ErrorResponse( HttpStatusCode.BAD_REQUEST, SAPI_ERROR_NAME, "Updating basket item failed", { failureKind: result.kind } ); } }, { method: "PUT" } ); export const getApplicablePromotionsByCode = defineRpcHandler( async ({ promotionCode, with: _with }, context) => { if (!hasSession(context)) { return new ErrorResponse( HttpStatusCode.BAD_REQUEST, HttpStatusMessage.BAD_REQUEST, "No Session found" ); } const { basketKey, sapiClient } = context; const resolvedWith = getWithParams({ with: _with }, context); const campaignKey = await context.callRpc?.("getCampaignKey"); const orderCustomData = await context.callRpc?.("getOrderCustomData"); const result = await mapSAPIFetchErrorToResponse( sapiClient.basket.getApplicablePromotionsByCode )(basketKey, promotionCode, { campaignKey, with: resolvedWith, orderCustomData, customerToken: getValidatedAccessToken(context.accessToken) ?? void 0 }); if (result instanceof Response || result.type === "failure") { context.log.info("Getting applicable promotion codes failed", { basketKey, promotionCode }); return new ErrorResponse( HttpStatusCode.BAD_REQUEST, SAPI_ERROR_NAME, "Getting applicable promotion codes failed" ); } else { return { basket: result.basket }; } }, { method: "POST" } ); export const updatePromotions = defineRpcHandler( async (params, context) => { if (!hasSession(context)) { return new ErrorResponse( HttpStatusCode.BAD_REQUEST, HttpStatusMessage.BAD_REQUEST, "No Session found" ); } const { basketKey, sapiClient } = context; const campaignKey = params.campaignKey ?? await context.callRpc?.("getCampaignKey"); const orderCustomData = await context.callRpc?.("getOrderCustomData"); const resolvedWith = getWithParams({ with: params.with }, context); const result = await mapSAPIFetchErrorToResponse( sapiClient.basket.bulkUpdatePromotions )(basketKey, { ...params, campaignKey, with: resolvedWith, orderCustomData, customerToken: getValidatedAccessToken(context.accessToken) ?? void 0 }); if (result instanceof Response || result.type === "failure") { context.log.info("Bulk updating promotions failed", { basketKey, items: params.items }); return new ErrorResponse( HttpStatusCode.BAD_REQUEST, SAPI_ERROR_NAME, "Bulk updating promotions failed" ); } return { basket: result.basket }; }, { method: "PUT" } ); function parseBasketError(response) { const parsedError = { message: "", statusCode: 500 }; if ("statusCode" in response) { parsedError.statusCode = response.statusCode; } else { const [error] = response.errors; parsedError.message = response.errors?.map( (error2) => `${error2.operation !== "delete" ? `${error2.kind}:` : ""}${error2.operation}:${error2.variantId} - ${error2.message}` ).join(" | "); parsedError.statusCode = error.statusCode; } return parsedError; }