Skip to content

Latest commit

 

History

History
241 lines (198 loc) · 7.59 KB

new-provider.md

File metadata and controls

241 lines (198 loc) · 7.59 KB

Adding a new Commerce Provider

🔔 New providers are on hold until we have a new API for commerce 🔔

A commerce provider is a headless e-commerce platform that integrates with the Commerce Framework. Right now we have the following providers:

Adding a commerce provider means adding a new folder in packages with a folder structure like the next one:

  • src
    • api
      • index.ts
    • product
      • usePrice
      • useSearch
      • getProduct
      • getAllProducts
    • wishlist
      • useWishlist
      • useAddItem
      • useRemoveItem
    • auth
      • useLogin
      • useLogout
      • useSignup
    • customer
      • useCustomer
      • getCustomerId
      • getCustomerWistlist
    • cart
      • useCart
      • useAddItem
      • useRemoveItem
      • useUpdateItem
    • index.ts
    • provider.ts
    • commerce.config.json
    • next.config.cjs
  • package.json
  • tsconfig.json
  • env.template
  • README.md

provider.ts exports a provider object with handlers for the Commerce Hooks and api/index.ts exports a Node.js provider for the Commerce API

Important: We use TypeScript for every provider and expect its usage for every new one.

The app imports from the provider directly instead of the core commerce folder (packages/commerce), but all providers are interchangeable and to achieve it every provider always has to implement the core types and helpers.

Updating the list of known providers

Open /site/commerce-config.js and add the provider name to the list in PROVIDERS.

Then, open /site/.env.template and add the provider name to the list there too.

Adding the provider hooks

Using BigCommerce as an example. The first thing to do is export a CommerceProvider component that includes a provider object with all the handlers that can be used for hooks:

import {
  getCommerceProvider,
  useCommerce as useCoreCommerce,
} from '@vercel/commerce'
import { bigcommerceProvider, BigcommerceProvider } from './provider'

export { bigcommerceProvider }
export type { BigcommerceProvider }

export const CommerceProvider = getCommerceProvider(bigcommerceProvider)

export const useCommerce = () => useCoreCommerce<BigcommerceProvider>()

The exported types and components extend from the core ones exported by @vercel/commerce, which refers to packages/commerce.

The bigcommerceProvider object looks like this:

import { handler as useCart } from './cart/use-cart'
import { handler as useAddItem } from './cart/use-add-item'
import { handler as useUpdateItem } from './cart/use-update-item'
import { handler as useRemoveItem } from './cart/use-remove-item'

import { handler as useWishlist } from './wishlist/use-wishlist'
import { handler as useWishlistAddItem } from './wishlist/use-add-item'
import { handler as useWishlistRemoveItem } from './wishlist/use-remove-item'

import { handler as useCustomer } from './customer/use-customer'
import { handler as useSearch } from './product/use-search'

import { handler as useLogin } from './auth/use-login'
import { handler as useLogout } from './auth/use-logout'
import { handler as useSignup } from './auth/use-signup'

import fetcher from './fetcher'

export const bigcommerceProvider = {
  locale: 'en-us',
  cartCookie: 'bc_cartId',
  fetcher,
  cart: { useCart, useAddItem, useUpdateItem, useRemoveItem },
  wishlist: {
    useWishlist,
    useAddItem: useWishlistAddItem,
    useRemoveItem: useWishlistRemoveItem,
  },
  customer: { useCustomer },
  products: { useSearch },
  auth: { useLogin, useLogout, useSignup },
}

export type BigcommerceProvider = typeof bigcommerceProvider

The provider object, in this case bigcommerceProvider, has to match the Provider type defined in packages/commerce.

A hook handler, like useCart, looks like this:

import { useMemo } from 'react'
import { SWRHook } from '@vercel/commerce/utils/types'
import useCart, { UseCart } from '@vercel/commerce/cart/use-cart'
import type { GetCartHook } from '@vercel/commerce/types/cart'

export default useCart as UseCart<typeof handler>

export const handler: SWRHook<GetCartHook> = {
  fetchOptions: {
    url: '/api/commerce/cart',
    method: 'GET',
  },
  useHook:
    ({ useData }) =>
    (input) => {
      const response = useData({
        swrOptions: { revalidateOnFocus: false, ...input?.swrOptions },
      })

      return useMemo(
        () =>
          Object.create(response, {
            isEmpty: {
              get() {
                return (response.data?.lineItems.length ?? 0) <= 0
              },
              enumerable: true,
            },
          }),
        [response]
      )
    },
}

In the case of data fetching hooks like useCart each handler has to implement the SWRHook type that's defined in the core types. For mutations it's the MutationHook, e.g for useAddItem:

import { useCallback } from 'react'
import type { MutationHook } from '@vercel/commerce/utils/types'
import { CommerceError } from '@vercel/commerce/utils/errors'
import useAddItem, { UseAddItem } from '@vercel/commerce/cart/use-add-item'
import type { AddItemHook } from '@vercel/commerce/types/cart'
import useCart from './use-cart'

export default useAddItem as UseAddItem<typeof handler>

export const handler: MutationHook<AddItemHook> = {
  fetchOptions: {
    url: '/api/commerce/cart',
    method: 'POST',
  },
  async fetcher({ input: item, options, fetch }) {
    if (
      item.quantity &&
      (!Number.isInteger(item.quantity) || item.quantity! < 1)
    ) {
      throw new CommerceError({
        message: 'The item quantity has to be a valid integer greater than 0',
      })
    }

    const data = await fetch({
      ...options,
      body: { item },
    })

    return data
  },
  useHook:
    ({ fetch }) =>
    () => {
      const { mutate } = useCart()

      return useCallback(
        async function addItem(input) {
          const data = await fetch({ input })
          await mutate(data, false)
          return data
        },
        [fetch, mutate]
      )
    },
}

Showing progress and features

When creating a PR for a new provider, include this list in the PR description and mark the progress as you push so we can organize the code review. Not all points are required (but advised) so make sure to keep the list up to date.

Status

  • CommerceProvider
  • Schema & TS types
  • API Operations - Get all collections
  • API Operations - Get all pages
  • API Operations - Get all products
  • API Operations - Get page
  • API Operations - Get product
  • API Operations - Get Shop Info (categories and vendors working — vendors query still a WIP PR on Reaction)
  • Hook - Add Item
  • Hook - Remove Item
  • Hook - Update Item
  • Hook - Get Cart (account-tied carts working, anonymous carts working, cart reconciliation working)
  • Auth (based on a WIP PR on Reaction - still need to implement refresh tokens)
  • Customer information
  • Product attributes - Size, Colors
  • Custom checkout
  • Typing (in progress)
  • Tests