diff --git a/framework/bigcommerce/api/cart/index.ts b/framework/bigcommerce/api/cart/index.ts
index 043097654..091cabf5a 100644
--- a/framework/bigcommerce/api/cart/index.ts
+++ b/framework/bigcommerce/api/cart/index.ts
@@ -1,8 +1,9 @@
-import { GetAPISchema } from '@commerce/api'
+import type { GetAPISchema } from '@commerce/api'
+import type { AddItemOperation } from '@commerce/types'
import getCart from './get-cart'
import addItem from './add-item'
-import updateItem from './handlers/update-item'
-import removeItem from './handlers/remove-item'
+import updateItem from './update-item'
+import removeItem from './remove-item'
import type {
GetCartHandlerBody,
AddCartItemHandlerBody,
@@ -18,14 +19,10 @@ export type CartAPI = GetAPISchema<
endpoint: {
options: {}
operations: {
- getCart: {
- data: Cart | null
- body: GetCartHandlerBody
- options: { yay: string }
- }
- addItem: { data: Cart; body: AddCartItemHandlerBody; options: {} }
- updateItem: { data: Cart; body: UpdateCartItemHandlerBody; options: {} }
- removeItem: { data: Cart; body: RemoveCartItemHandlerBody; options: {} }
+ getCart: { data: Cart | null; body: GetCartHandlerBody }
+ addItem: { data: Cart; body: AddItemOperation['body'] }
+ updateItem: { data: Cart; body: UpdateCartItemHandlerBody }
+ removeItem: { data: Cart; body: RemoveCartItemHandlerBody }
}
}
}
@@ -33,4 +30,4 @@ export type CartAPI = GetAPISchema<
export type CartEndpoint = CartAPI['endpoint']
-export const operations = { getCart, addItem }
+export const operations = { getCart, addItem, updateItem, removeItem }
diff --git a/framework/bigcommerce/api/cart/remove-item.ts b/framework/bigcommerce/api/cart/remove-item.ts
new file mode 100644
index 000000000..18c641260
--- /dev/null
+++ b/framework/bigcommerce/api/cart/remove-item.ts
@@ -0,0 +1,34 @@
+import { normalizeCart } from '@framework/lib/normalize'
+import getCartCookie from '../utils/get-cart-cookie'
+import type { CartEndpoint } from '.'
+
+const removeItem: CartEndpoint['operations']['removeItem'] = async ({
+ res,
+ body: { cartId, itemId },
+ config,
+}) => {
+ if (!cartId || !itemId) {
+ return res.status(400).json({
+ data: null,
+ errors: [{ message: 'Invalid request' }],
+ })
+ }
+
+ const result = await config.storeApiFetch<{ data: any } | null>(
+ `/v3/carts/${cartId}/items/${itemId}?include=line_items.physical_items.options`,
+ { method: 'DELETE' }
+ )
+ const data = result?.data ?? null
+
+ res.setHeader(
+ 'Set-Cookie',
+ 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)
+ )
+ res.status(200).json({ data: normalizeCart(data) })
+}
+
+export default removeItem
diff --git a/framework/bigcommerce/api/cart/update-item.ts b/framework/bigcommerce/api/cart/update-item.ts
new file mode 100644
index 000000000..b283e24a3
--- /dev/null
+++ b/framework/bigcommerce/api/cart/update-item.ts
@@ -0,0 +1,36 @@
+import { normalizeCart } from '@framework/lib/normalize'
+import { parseCartItem } from '../utils/parse-item'
+import getCartCookie from '../utils/get-cart-cookie'
+import type { CartEndpoint } from '.'
+
+const updateItem: CartEndpoint['operations']['updateItem'] = async ({
+ res,
+ body: { cartId, itemId, item },
+ config,
+}) => {
+ 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}?include=line_items.physical_items.options`,
+ {
+ method: 'PUT',
+ body: JSON.stringify({
+ line_item: parseCartItem(item),
+ }),
+ }
+ )
+
+ // Update the cart cookie
+ res.setHeader(
+ 'Set-Cookie',
+ getCartCookie(config.cartCookie, cartId, config.cartCookieMaxAge)
+ )
+ res.status(200).json({ data: normalizeCart(data) })
+}
+
+export default updateItem
diff --git a/framework/bigcommerce/api/index.ts b/framework/bigcommerce/api/index.ts
index d11f168b8..ebfca8890 100644
--- a/framework/bigcommerce/api/index.ts
+++ b/framework/bigcommerce/api/index.ts
@@ -1,3 +1,4 @@
+import type { NextApiHandler } from 'next'
import type { RequestInit } from '@vercel/fetch'
import {
CommerceAPI as CoreCommerceAPI,
@@ -6,6 +7,8 @@ import {
import fetchGraphqlApi from './utils/fetch-graphql-api'
import fetchStoreApi from './utils/fetch-store-api'
+import type { CartAPI } from './cart'
+
export interface BigcommerceConfig extends CommerceAPIConfig {
// Indicates if the returned metadata with translations should be applied to the
// data or returned as it is
@@ -104,11 +107,20 @@ export const provider = {
export type Provider = typeof provider
-export class CommerceAPI<
- P extends Provider = Provider
-> extends CoreCommerceAPI
{
- constructor(readonly provider: P = provider) {
- super(provider)
+export type APIs = CartAPI
+
+export class CommerceAPI extends CoreCommerceAPI {
+ constructor(customProvider: Provider = provider) {
+ super(customProvider)
+ }
+
+ endpoint(
+ context: E['endpoint'] & {
+ config?: Provider['config']
+ options?: E['schema']['endpoint']['options']
+ }
+ ): NextApiHandler {
+ return this.endpoint(context)
}
}
diff --git a/framework/bigcommerce/types.ts b/framework/bigcommerce/types.ts
index d0d711f3d..beeab0223 100644
--- a/framework/bigcommerce/types.ts
+++ b/framework/bigcommerce/types.ts
@@ -25,7 +25,6 @@ export type BigcommerceCart = {
export type Cart = Core.Cart & {
lineItems: LineItem[]
- core: string
}
export type LineItem = Core.LineItem
diff --git a/framework/commerce/api/index.ts b/framework/commerce/api/index.ts
index c8d1d78f7..1d200fc82 100644
--- a/framework/commerce/api/index.ts
+++ b/framework/commerce/api/index.ts
@@ -1,19 +1,7 @@
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 CartSchema = {
- endpoint: {
- options: {}
- operations: {
- getCart: { data?: Cart | null; body?: any }
- addItem: { data?: Cart; body?: any }
- updateItem: { data?: Cart; body?: any }
- removeItem: { data?: Cart; body?: any }
- }
- }
-}
+import type { CartSchema } from '../types'
export type APISchemas = CartSchema
@@ -78,10 +66,10 @@ export class CommerceAPI {
Object.assign(this.provider.config, newConfig)
}
- endpoint>(
- context: E['endpoint'] & {
+ endpoint>(
+ context: T['endpoint'] & {
config?: P['config']
- options?: E['schema']['endpoint']['options']
+ options?: T['schema']['endpoint']['options']
}
): NextApiHandler {
const commerce = this
@@ -93,7 +81,7 @@ export class CommerceAPI {
res,
commerce,
config: cfg,
- handlers: context.operations,
+ operations: context.operations,
options: context.options ?? {},
})
}
diff --git a/framework/commerce/api/utils/types.ts b/framework/commerce/api/utils/types.ts
index 27a95df40..41055b606 100644
--- a/framework/commerce/api/utils/types.ts
+++ b/framework/commerce/api/utils/types.ts
@@ -20,7 +20,7 @@ export type APIHandlerContext<
res: NextApiResponse>
commerce: C
config: C['provider']['config']
- handlers: H
+ operations: H
/**
* Custom configs that may be used by a particular handler
*/
diff --git a/framework/commerce/types/cart.ts b/framework/commerce/types/cart.ts
new file mode 100644
index 000000000..f2873b005
--- /dev/null
+++ b/framework/commerce/types/cart.ts
@@ -0,0 +1,147 @@
+import type { Discount, Measurement, Image } from './common'
+
+export type LineItem = {
+ id: string
+ variantId: string
+ productId: string
+ name: string
+ quantity: number
+ discounts: Discount[]
+ // A human-friendly unique string automatically generated from the product’s name
+ path: string
+ variant: ProductVariant
+}
+
+export type ProductVariant = {
+ id: string
+ // The SKU (stock keeping unit) associated with the product variant.
+ sku: string
+ // The product variant’s title, or the product's name.
+ name: string
+ // Whether a customer needs to provide a shipping address when placing
+ // an order for the product variant.
+ requiresShipping: boolean
+ // The product variant’s price after all discounts are applied.
+ price: number
+ // Product variant’s price, as quoted by the manufacturer/distributor.
+ listPrice: number
+ // Image associated with the product variant. Falls back to the product image
+ // if no image is available.
+ image?: Image
+ // Indicates whether this product variant is in stock.
+ isInStock?: boolean
+ // Indicates if the product variant is available for sale.
+ availableForSale?: boolean
+ // The variant's weight. If a weight was not explicitly specified on the
+ // variant this will be the product's weight.
+ weight?: Measurement
+ // The variant's height. If a height was not explicitly specified on the
+ // variant, this will be the product's height.
+ height?: Measurement
+ // The variant's width. If a width was not explicitly specified on the
+ // variant, this will be the product's width.
+ width?: Measurement
+ // The variant's depth. If a depth was not explicitly specified on the
+ // variant, this will be the product's depth.
+ depth?: Measurement
+}
+
+// Shopping cart, a.k.a Checkout
+export type Cart = {
+ id: string
+ // ID of the customer to which the cart belongs.
+ customerId?: string
+ // The email assigned to this cart
+ email?: string
+ // The date and time when the cart was created.
+ createdAt: string
+ // The currency used for this cart
+ currency: { code: string }
+ // Specifies if taxes are included in the line items.
+ taxesIncluded: boolean
+ lineItems: LineItem[]
+ // The sum of all the prices of all the items in the cart.
+ // Duties, taxes, shipping and discounts excluded.
+ lineItemsSubtotalPrice: number
+ // Price of the cart before duties, shipping and taxes.
+ subtotalPrice: number
+ // The sum of all the prices of all the items in the cart.
+ // Duties, taxes and discounts included.
+ totalPrice: number
+ // Discounts that have been applied on the cart.
+ discounts?: Discount[]
+}
+
+/**
+ * Base cart item body used for cart mutations
+ */
+export type CartItemBody = {
+ variantId: string
+ productId?: string
+ quantity?: number
+}
+
+/**
+ * Hooks schema
+ */
+
+export type CartHooks = {
+ getCart: GetCartHook
+ addItem: AddItemHook
+ updateItem: UpdateItemHook
+ remoteItem: RemoveItemHook
+}
+
+export type GetCartHook = {
+ data: Cart | null
+}
+
+export type AddItemHook = {
+ data: Cart
+ body: { item: CartItemBody }
+}
+
+export type UpdateItemHook = {
+ data: Cart
+ body: { itemId: string; item: CartItemBody }
+}
+
+export type RemoveItemHook = {
+ data: Cart | null
+ body: { itemId: string }
+}
+
+/**
+ * API Schema
+ */
+
+export type CartSchema = {
+ endpoint: {
+ options: {}
+ operations: CartOperations
+ }
+}
+
+export type CartOperations = {
+ getCart: GetCartOperation
+ addItem: AddItemOperation
+ updateItem: UpdateItemOperation
+ removeItem: RemoveItemOperation
+}
+
+export type GetCartOperation = {
+ data: Cart | null
+ body: { cartId?: string }
+}
+
+export type AddItemOperation = AddItemHook & {
+ body: { cartId: string }
+}
+
+export type UpdateItemOperation = UpdateItemHook & {
+ body: { cartId: string }
+}
+
+export type RemoveItemOperation = RemoveItemHook & {
+ body: { cartId: string }
+}
diff --git a/framework/commerce/types/common.ts b/framework/commerce/types/common.ts
new file mode 100644
index 000000000..06908c464
--- /dev/null
+++ b/framework/commerce/types/common.ts
@@ -0,0 +1,16 @@
+export type Discount = {
+ // The value of the discount, can be an amount or percentage
+ value: number
+}
+
+export type Measurement = {
+ value: number
+ unit: 'KILOGRAMS' | 'GRAMS' | 'POUNDS' | 'OUNCES'
+}
+
+export type Image = {
+ url: string
+ altText?: string
+ width?: number
+ height?: number
+}
diff --git a/framework/commerce/types/index.ts b/framework/commerce/types/index.ts
new file mode 100644
index 000000000..057b4c420
--- /dev/null
+++ b/framework/commerce/types/index.ts
@@ -0,0 +1,130 @@
+import type { Wishlist as BCWishlist } from '../../bigcommerce/api/wishlist'
+import type { Customer as BCCustomer } from '../../bigcommerce/api/customers'
+import type { SearchProductsData as BCSearchProductsData } from '../../bigcommerce/api/catalog/products'
+
+export * from './cart'
+export * from './common'
+
+// TODO: Properly define this type
+export interface Wishlist extends BCWishlist {}
+
+// TODO: Properly define this type
+export interface Customer extends BCCustomer {}
+
+// TODO: Properly define this type
+export interface SearchProductsData extends BCSearchProductsData {}
+
+/**
+ * Cart mutations
+ */
+
+// Base cart item body used for cart mutations
+export type CartItemBody = {
+ variantId: string
+ productId?: string
+ quantity?: number
+}
+
+// Body used by the `getCart` operation handler
+export type GetCartHandlerBody = {
+ cartId?: string
+}
+
+// Body used by the add item to cart operation
+export type AddCartItemBody = {
+ item: T
+}
+
+// Body expected by the add item to cart operation handler
+export type AddCartItemHandlerBody = Partial<
+ AddCartItemBody
+> & {
+ cartId?: string
+}
+
+// Body used by the update cart item operation
+export type UpdateCartItemBody = {
+ itemId: string
+ item: T
+}
+
+// Body expected by the update cart item operation handler
+export type UpdateCartItemHandlerBody = Partial<
+ UpdateCartItemBody
+> & {
+ cartId?: string
+}
+
+// Body used by the remove cart item operation
+export type RemoveCartItemBody = {
+ itemId: string
+}
+
+// Body expected by the remove cart item operation handler
+export type RemoveCartItemHandlerBody = Partial & {
+ cartId?: string
+}
+
+/**
+ * Temporal types
+ */
+
+interface Entity {
+ id: string | number
+ [prop: string]: any
+}
+
+export interface Product2 {
+ id: string
+ name: string
+ description: string
+ sku?: string
+ slug?: string
+ path?: string
+ images: ProductImage[]
+ variants: ProductVariant2[]
+ price: ProductPrice
+ options: ProductOption[]
+}
+
+export interface Product extends Entity {
+ name: string
+ description: string
+ slug?: string
+ path?: string
+ images: ProductImage[]
+ variants: ProductVariant2[]
+ price: ProductPrice
+ options: ProductOption[]
+ sku?: string
+}
+
+interface ProductOption extends Entity {
+ displayName: string
+ values: ProductOptionValues[]
+}
+
+interface ProductOptionValues {
+ label: string
+ hexColors?: string[]
+}
+
+interface ProductImage {
+ url: string
+ alt?: string
+}
+
+interface ProductVariant2 {
+ id: string | number
+ options: ProductOption[]
+}
+
+interface ProductPrice {
+ value: number
+ currencyCode: 'USD' | 'ARS' | string | undefined
+ retailPrice?: number
+ salePrice?: number
+ listPrice?: number
+ extendedSalePrice?: number
+ extendedListPrice?: number
+}
diff --git a/pages/api/bigcommerce/cart.ts b/pages/api/bigcommerce/cart.ts
index ee9e1e04c..4bb74cb2f 100644
--- a/pages/api/bigcommerce/cart.ts
+++ b/pages/api/bigcommerce/cart.ts
@@ -1,5 +1,8 @@
import cart from '@commerce/api/endpoints/cart'
-import { operations } from '@framework/api/cart'
+import { CartAPI, operations } from '@framework/api/cart'
import commerce from '@lib/api/commerce'
-export default commerce.endpoint({ handler: cart, operations })
+export default commerce.endpoint({
+ handler: cart as CartAPI['endpoint']['handler'],
+ operations,
+})