diff --git a/framework/bigcommerce/api/cart/add-item.ts b/framework/bigcommerce/api/cart/add-item.ts new file mode 100644 index 000000000..8c83c4f7b --- /dev/null +++ b/framework/bigcommerce/api/cart/add-item.ts @@ -0,0 +1,46 @@ +import { parseCartItem } from '../utils/parse-item' +import getCartCookie from '../utils/get-cart-cookie' +import type { CartHandlers } from '.' +import { normalizeCart } from '@framework/lib/normalize' + +const addItem: CartHandlers['addItem'] = async ({ + res, + body: { cartId, item }, + config, +}) => { + if (!item) { + return res.status(400).json({ + data: null, + errors: [{ message: 'Missing item' }], + }) + } + if (!item.quantity) item.quantity = 1 + + const options = { + method: 'POST', + body: JSON.stringify({ + line_items: [parseCartItem(item)], + ...(!cartId && config.storeChannelId + ? { channel_id: config.storeChannelId } + : {}), + }), + } + const { data } = cartId + ? await config.storeApiFetch( + `/v3/carts/${cartId}/items?include=line_items.physical_items.options`, + options + ) + : await config.storeApiFetch( + '/v3/carts?include=line_items.physical_items.options', + options + ) + + // Create or update the cart cookie + res.setHeader( + 'Set-Cookie', + getCartCookie(config.cartCookie, data.id, config.cartCookieMaxAge) + ) + res.status(200).json({ data: normalizeCart(data) }) +} + +export default addItem diff --git a/framework/bigcommerce/api/cart/index.ts b/framework/bigcommerce/api/cart/index.ts index 4ee668895..22adb57d4 100644 --- a/framework/bigcommerce/api/cart/index.ts +++ b/framework/bigcommerce/api/cart/index.ts @@ -1,7 +1,7 @@ +import { CartHandlers as CartHandlersCore } from '@commerce/api' import isAllowedMethod from '../utils/is-allowed-method' import createApiHandler, { BigcommerceApiHandler, - BigcommerceHandler, } from '../utils/create-api-handler' import { BigcommerceApiError } from '../utils/errors' import getCart from './handlers/get-cart' @@ -14,14 +14,22 @@ import type { AddCartItemHandlerBody, UpdateCartItemHandlerBody, RemoveCartItemHandlerBody, + Cart, } from '../../types' +import { APIHandler } from '@commerce/api/utils/types' +import { CommerceAPI } from '..' -export type CartHandlers = { - getCart: BigcommerceHandler - addItem: BigcommerceHandler - updateItem: BigcommerceHandler - removeItem: BigcommerceHandler -} +export type CartHandlers< + C extends CommerceAPI = CommerceAPI +> = CartHandlersCore< + C, + { + getCart: APIHandler, Cart | null, GetCartHandlerBody> + addItem: APIHandler, Cart, AddCartItemHandlerBody> + updateItem: APIHandler, Cart, UpdateCartItemHandlerBody> + removeItem: APIHandler, Cart, RemoveCartItemHandlerBody> + } +> const METHODS = ['GET', 'POST', 'PUT', 'DELETE'] diff --git a/framework/bigcommerce/api/index.ts b/framework/bigcommerce/api/index.ts index 840645459..d11f168b8 100644 --- a/framework/bigcommerce/api/index.ts +++ b/framework/bigcommerce/api/index.ts @@ -104,7 +104,9 @@ export const provider = { export type Provider = typeof provider -export class CommerceAPI

extends CoreCommerceAPI

{ +export class CommerceAPI< + P extends Provider = Provider +> extends CoreCommerceAPI

{ constructor(readonly provider: P = provider) { super(provider) } diff --git a/framework/commerce/api/endpoints/cart.ts b/framework/commerce/api/endpoints/cart.ts index 32c73f264..7bcb2c46f 100644 --- a/framework/commerce/api/endpoints/cart.ts +++ b/framework/commerce/api/endpoints/cart.ts @@ -1,9 +1,9 @@ import type { APIEndpoint } from '../utils/types' import { CommerceAPIError } from '../utils/errors' import isAllowedOperation from '../utils/is-allowed-operation' -import type { APIProvider, CartHandlers } from '..' +import type { CommerceAPI, CartHandlers } from '..' -const cartApi: APIEndpoint> = async (ctx) => { +const cartApi: APIEndpoint> = async (ctx) => { const { req, res, handlers, config } = ctx if ( diff --git a/framework/commerce/api/index.ts b/framework/commerce/api/index.ts index 85079bb34..e2bc4f225 100644 --- a/framework/commerce/api/index.ts +++ b/framework/commerce/api/index.ts @@ -1,16 +1,22 @@ import type { NextApiHandler } from 'next' import type { RequestInit, Response } from '@vercel/fetch' import type { APIEndpoint, APIHandler } from './utils/types' +import type { Cart } from '../types' -export type CartEndpoint = APIEndpoint, any> +export type CartEndpoint = APIEndpoint, any> -export type CartHandlers = { - getCart: APIHandler, any, Body> - addItem: APIHandler, any, Body> - updateItem: APIHandler, any, Body> - removeItem: APIHandler, any, Body> +export type CartHandlersBase = { + getCart: APIHandler, Cart | null, any> + addItem: APIHandler, Cart, any> + updateItem: APIHandler, Cart, any> + removeItem: APIHandler, Cart, any> } +export type CartHandlers< + C extends CommerceAPI, + T extends CartHandlersBase = CartHandlersBase +> = T + export type Endpoints = CartEndpoint export type EndpointHandlers = E extends APIEndpoint @@ -21,17 +27,12 @@ export type EndpointOptions = E extends APIEndpoint ? T : never -export type CoreAPIProvider = { +export type APIProvider = { config: CommerceAPIConfig } -export type APIProvider

= P & { - getConfig(userConfig?: Partial): P['config'] - setConfig(newConfig: Partial): void -} - export class CommerceAPI< - P extends CoreAPIProvider = CoreAPIProvider, + P extends APIProvider = APIProvider, E extends Endpoints = Endpoints > { constructor(readonly provider: P) { @@ -55,14 +56,14 @@ export class CommerceAPI< operations: EndpointHandlers options?: EndpointOptions }): NextApiHandler { - const provider = this + const commerce = this const cfg = this.getConfig(context.config) return function apiHandler(req, res) { return context.handler({ req, res, - provider, + commerce, config: cfg, handlers: context.operations, options: context.options, @@ -71,23 +72,6 @@ export class CommerceAPI< } } -export function createAPIProvider

( - provider: P -): APIProvider

{ - return { - ...provider, - getConfig(userConfig = {}) { - return Object.entries(userConfig).reduce( - (cfg, [key, value]) => Object.assign(cfg, { [key]: value }), - { ...this.config } - ) - }, - setConfig(newConfig) { - Object.assign(this.config, newConfig) - }, - } -} - export interface CommerceAPIConfig { locale?: string commerceUrl: string diff --git a/framework/commerce/api/utils/types.ts b/framework/commerce/api/utils/types.ts index fcf2057dd..c971d64f8 100644 --- a/framework/commerce/api/utils/types.ts +++ b/framework/commerce/api/utils/types.ts @@ -1,21 +1,25 @@ -import type { NextApiHandler, NextApiRequest, NextApiResponse } from 'next' -import type { APIProvider } from '..' +import type { NextApiRequest, NextApiResponse } from 'next' +import type { CommerceAPI } from '..' -export type APIResponse = { - data: Data - errors?: { message: string; code?: string }[] -} +export type ErrorData = { message: string; code?: string } + +export type APIResponse = + | { data: Data; errors?: ErrorData[] } + // If `data` doesn't include `null`, then `null` is only allowed on errors + | (Data extends null + ? { data: null; errors?: ErrorData[] } + : { data: null; errors: ErrorData[] }) export type APIHandlerContext< - P extends APIProvider, - H extends APIHandlers = {}, + C extends CommerceAPI, + H extends APIHandlers = {}, Data = any, Options extends {} = {} > = { req: NextApiRequest res: NextApiResponse> - provider: P - config: P['config'] + commerce: C + config: C['provider']['config'] handlers: H /** * Custom configs that may be used by a particular handler @@ -24,22 +28,22 @@ export type APIHandlerContext< } export type APIHandler< - P extends APIProvider, - H extends APIHandlers = {}, + C extends CommerceAPI, + H extends APIHandlers = {}, Data = any, Body = any, Options extends {} = {} > = ( - context: APIHandlerContext & { body: Body } + context: APIHandlerContext & { body: Body } ) => void | Promise -export type APIHandlers

= { - [k: string]: APIHandler +export type APIHandlers = { + [k: string]: APIHandler } export type APIEndpoint< - P extends APIProvider = APIProvider, - H extends APIHandlers = {}, + C extends CommerceAPI = CommerceAPI, + H extends APIHandlers = {}, Data = any, Options extends {} = {} -> = (context: APIHandlerContext) => void | Promise +> = (context: APIHandlerContext) => void | Promise