4
0
forked from crowetic/commerce

196 lines
5.2 KiB
TypeScript
Raw Normal View History

2020-10-03 16:06:41 -05:00
import { serialize, CookieSerializeOptions } from 'cookie'
import isAllowedMethod from './utils/is-allowed-method'
import createApiHandler, {
BigcommerceApiHandler,
} from './utils/create-api-handler'
import { BigcommerceApiError } from './utils/errors'
type Body<T> = Partial<T> | undefined
export type ItemBody = {
2020-10-04 19:44:11 -05:00
productId: number
variantId: number
quantity?: number
}
export type AddItemBody = { item: ItemBody }
export type UpdateItemBody = { itemId: string; item: ItemBody }
export type RemoveItemBody = { itemId: string }
2020-10-04 21:20:52 -05:00
// TODO: this type should match:
// https://developer.bigcommerce.com/api-reference/cart-checkout/server-server-cart-api/cart/getacart#responses
export type Cart = {
id: string
parent_id?: string
customer_id: number
email: string
currency: { code: string }
tax_included: boolean
base_amount: number
discount_amount: number
cart_amount: number
2020-10-05 01:05:04 -05:00
line_items: {
custom_items: any[]
digital_items: any[]
gift_certificates: any[]
physical_items: any[]
2020-10-05 01:05:04 -05:00
}
2020-10-04 21:20:52 -05:00
// TODO: add missing fields
}
2020-10-03 16:06:41 -05:00
const METHODS = ['GET', 'POST', 'PUT', 'DELETE']
// TODO: a complete implementation should have schema validation for `req.body`
2020-10-04 19:44:11 -05:00
const cartApi: BigcommerceApiHandler<Cart> = async (req, res, config) => {
2020-10-03 16:06:41 -05:00
if (!isAllowedMethod(req, res, METHODS)) return
const { cookies } = req
const cartId = cookies[config.cartCookie]
2020-10-03 20:49:09 -05:00
try {
// Return current cart info
if (req.method === 'GET') {
let result: { data?: Cart } = {}
2020-10-03 16:06:41 -05:00
2020-10-03 20:49:09 -05:00
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
2020-10-04 19:44:11 -05:00
res.setHeader('Set-Cookie', getCartCookie(config.cartCookie))
2020-10-03 20:49:09 -05:00
} else {
throw error
}
2020-10-03 16:06:41 -05:00
}
2020-10-03 20:49:09 -05:00
2020-10-04 13:46:28 -05:00
return res.status(200).json({ data: result.data ?? null })
2020-10-03 16:06:41 -05:00
}
2020-10-05 10:28:16 -05:00
// Create or add an item to the cart
2020-10-03 20:49:09 -05:00
if (req.method === 'POST') {
const { item } = (req.body as Body<AddItemBody>) ?? {}
2020-10-03 20:49:09 -05:00
2020-10-04 19:44:11 -05:00
if (!item) {
2020-10-03 20:49:09 -05:00
return res.status(400).json({
2020-10-04 19:44:11 -05:00
data: null,
2020-10-05 10:28:16 -05:00
errors: [{ message: 'Missing item' }],
2020-10-03 20:49:09 -05:00
})
}
if (!item.quantity) item.quantity = 1
2020-10-03 20:49:09 -05:00
const options = {
method: 'POST',
body: JSON.stringify({
2020-10-04 19:44:11 -05:00
line_items: [parseItem(item)],
2020-10-03 20:49:09 -05:00
}),
}
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',
2020-10-04 19:44:11 -05:00
getCartCookie(config.cartCookie, data.id, config.cartCookieMaxAge)
2020-10-03 20:49:09 -05:00
)
2020-10-04 19:44:11 -05:00
return res.status(200).json({ data })
2020-10-03 20:49:09 -05:00
}
2020-10-05 10:28:16 -05:00
// Update item in cart
if (req.method === 'PUT') {
const { itemId, item } = (req.body as Body<UpdateItemBody>) ?? {}
2020-10-05 10:28:16 -05:00
if (!cartId || !itemId || !item) {
return res.status(400).json({
data: null,
errors: [{ message: 'Invalid request' }],
})
}
const { data } = await config.storeApiFetch(
`/v3/carts/${cartId}/items/${itemId}`,
{
method: 'PUT',
body: JSON.stringify({
2020-10-06 19:07:02 -05:00
line_item: parseItem(item),
2020-10-05 10:28:16 -05:00
}),
}
)
// Update the cart cookie
res.setHeader(
'Set-Cookie',
getCartCookie(config.cartCookie, cartId, config.cartCookieMaxAge)
)
return res.status(200).json({ data })
}
// Remove an item from the cart
if (req.method === 'DELETE') {
const { itemId } = (req.body as Body<RemoveItemBody>) ?? {}
if (!cartId || !itemId) {
return res.status(400).json({
data: null,
errors: [{ message: 'Invalid request' }],
})
}
2020-10-07 12:56:51 -05:00
const result = await config.storeApiFetch<{ data: any } | null>(
`/v3/carts/${cartId}/items/${itemId}`,
2020-10-07 12:56:51 -05:00
{ method: 'DELETE' }
)
2020-10-07 15:08:17 -05:00
const data = result?.data ?? null
res.setHeader(
'Set-Cookie',
2020-10-07 15:08:17 -05:00
data
? // Update the cart cookie
getCartCookie(config.cartCookie, cartId, config.cartCookieMaxAge)
: // Remove the cart cookie if the cart was removed (empty items)
getCartCookie(config.cartCookie)
)
2020-10-07 15:08:17 -05:00
return res.status(200).json({ data })
}
2020-10-03 20:49:09 -05:00
} catch (error) {
2020-10-04 19:44:11 -05:00
console.error(error)
2020-10-03 20:49:09 -05:00
const message =
error instanceof BigcommerceApiError
? 'An unexpected error ocurred with the Bigcommerce API'
: 'An unexpected error ocurred'
2020-10-04 19:44:11 -05:00
res.status(500).json({ data: null, errors: [{ message }] })
2020-10-03 16:06:41 -05:00
}
}
2020-10-03 20:49:09 -05:00
function getCartCookie(name: string, cartId?: string, maxAge?: number) {
const options: CookieSerializeOptions =
cartId && maxAge
? {
maxAge,
expires: new Date(Date.now() + maxAge * 1000),
secure: process.env.NODE_ENV === 'production',
path: '/',
sameSite: 'lax',
}
: { maxAge: -1, path: '/' } // Removes the cookie
2020-10-03 16:06:41 -05:00
return serialize(name, cartId || '', options)
}
const parseItem = (item: ItemBody) => ({
quantity: item.quantity,
2020-10-04 19:44:11 -05:00
product_id: item.productId,
variant_id: item.variantId,
2020-10-03 20:49:09 -05:00
})
2020-10-03 16:06:41 -05:00
export default createApiHandler(cartApi)