From ca959f6491ac63d62f9c725d4690972eb021d926 Mon Sep 17 00:00:00 2001 From: Luis Alvarez Date: Sat, 3 Oct 2020 20:49:09 -0500 Subject: [PATCH] Added add to cart method --- lib/bigcommerce/api/cart.ts | 100 ++++++++++++++++++++-------- lib/bigcommerce/api/index.ts | 2 + lib/bigcommerce/api/utils/errors.ts | 13 +--- lib/commerce/api/index.ts | 1 + 4 files changed, 76 insertions(+), 40 deletions(-) diff --git a/lib/bigcommerce/api/cart.ts b/lib/bigcommerce/api/cart.ts index ebf5c50b1..e21d3c436 100644 --- a/lib/bigcommerce/api/cart.ts +++ b/lib/bigcommerce/api/cart.ts @@ -15,43 +15,87 @@ const cartApi: BigcommerceApiHandler = async (req, res, config) => { const { cookies } = req const cartId = cookies[config.cartCookie] - // Return current cart info - if (req.method === 'GET') { - let result: { data?: Cart } = {} + try { + // Return current cart info + if (req.method === 'GET') { + let result: { data?: Cart } = {} - try { - result = await config.storeApiFetch( - `/v3/carts/${cartId}?include=redirect_urls` - ) - } catch (error) { - if (error instanceof BigcommerceApiError && error.status === 404) { - // Remove the cookie if it exists but the cart wasn't found - res.setHeader('Set-Cookie', getCartCookie(name)) - } else { - throw error + try { + result = await config.storeApiFetch( + `/v3/carts/${cartId}?include=redirect_urls` + ) + } catch (error) { + if (error instanceof BigcommerceApiError && error.status === 404) { + // Remove the cookie if it exists but the cart wasn't found + res.setHeader('Set-Cookie', getCartCookie(name)) + } else { + throw error + } } + + return res.status(200).json({ cart: result.data ?? null }) } - return res.status(200).json({ cart: result.data ?? null }) + // Create or add a product to the cart + if (req.method === 'POST') { + const { product } = req.body + + if (!product) { + return res.status(400).json({ + errors: [{ message: 'Missing product' }], + }) + } + + const options = { + method: 'POST', + body: JSON.stringify({ + line_items: [parseProduct(product)], + }), + } + const { data } = cartId + ? await config.storeApiFetch(`/v3/carts/${cartId}/items`, options) + : await config.storeApiFetch('/v3/carts', options) + + // Create or update the cart cookie + res.setHeader( + 'Set-Cookie', + getCartCookie(name, data.id, config.cartCookieMaxAge) + ) + + // There's no need to send any additional data here, the UI can use this response to display a + // "success" for the operation and revalidate the GET request for this same endpoint right after. + return res.status(200).json({ done: true }) + } + } catch (error) { + const message = + error instanceof BigcommerceApiError + ? 'An unexpected error ocurred with the Bigcommerce API' + : 'An unexpected error ocurred' + + res.status(500).json({ errors: [{ message }] }) } } -const ONE_DAY = 60 * 60 * 24 -const MAX_AGE = ONE_DAY * 30 - -function getCartCookie(name: string, cartId?: string) { - const options: CookieSerializeOptions = cartId - ? { - maxAge: MAX_AGE, - expires: new Date(Date.now() + MAX_AGE * 1000), - httpOnly: true, - secure: process.env.NODE_ENV === 'production', - path: '/', - sameSite: 'lax', - } - : { maxAge: -1, path: '/' } // Removes the cookie +function getCartCookie(name: string, cartId?: string, maxAge?: number) { + const options: CookieSerializeOptions = + cartId && maxAge + ? { + maxAge, + expires: new Date(Date.now() + maxAge * 1000), + httpOnly: true, + secure: process.env.NODE_ENV === 'production', + path: '/', + sameSite: 'lax', + } + : { maxAge: -1, path: '/' } // Removes the cookie return serialize(name, cartId || '', options) } +const parseProduct = (product: any) => ({ + quantity: product.quantity, + product_id: product.productId, + variant_id: product.variantId, +}) + export default createApiHandler(cartApi) diff --git a/lib/bigcommerce/api/index.ts b/lib/bigcommerce/api/index.ts index 752ef038d..69e26f523 100644 --- a/lib/bigcommerce/api/index.ts +++ b/lib/bigcommerce/api/index.ts @@ -103,10 +103,12 @@ export class Config { } } +const ONE_DAY = 60 * 60 * 24 const config = new Config({ commerceUrl: API_URL, apiToken: API_TOKEN, cartCookie: process.env.BIGCOMMERCE_CART_COOKIE ?? 'bc_cartId', + cartCookieMaxAge: ONE_DAY * 30, fetch: fetchGraphqlApi, // REST API only storeApiUrl: STORE_API_URL, diff --git a/lib/bigcommerce/api/utils/errors.ts b/lib/bigcommerce/api/utils/errors.ts index 70f97953e..1cdd72214 100644 --- a/lib/bigcommerce/api/utils/errors.ts +++ b/lib/bigcommerce/api/utils/errors.ts @@ -1,16 +1,5 @@ // Used for GraphQL errors -export class BigcommerceError extends Error { - status?: number - - constructor(msg: string, res?: Response) { - super(msg) - this.name = 'BigcommerceError' - - if (res) { - this.status = res.status - } - } -} +export class BigcommerceGraphQLError extends Error {} export class BigcommerceApiError extends Error { status: number diff --git a/lib/commerce/api/index.ts b/lib/commerce/api/index.ts index dcf95498f..6c4bf0544 100644 --- a/lib/commerce/api/index.ts +++ b/lib/commerce/api/index.ts @@ -2,6 +2,7 @@ export interface CommerceAPIConfig { commerceUrl: string apiToken: string cartCookie: string + cartCookieMaxAge: number fetch( query: string, queryData?: CommerceAPIFetchOptions