@scayle/storefront-core
Version:
Collection of essential utilities to work with the Storefront API
432 lines (431 loc) • 13.6 kB
JavaScript
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;
}