Dynamic API routes (#836)

* Add dynamic API endpoints

* Add missing dependency

* Update api handlers

* Updates

* Fix build errors

* Update package.json

* Add checkout endpoint parser & update errors

* Update tsconfig.json

* Update cart.ts

* Update parser

* Update errors.ts

* Update errors.ts

* Move to Edge runtime

* Revert to local

* Fix switchable runtimes

* Make nodejs default runtime

* Update pnpm-lock.yaml

* Update handlers

* Fix build errors

* Change headers
This commit is contained in:
Catalin Pinte
2022-10-30 20:41:21 +02:00
committed by GitHub
parent a5b367a747
commit c75b0fc001
316 changed files with 2482 additions and 2176 deletions

View File

@@ -48,10 +48,7 @@
},
"dependencies": {
"@vercel/commerce": "workspace:*",
"@vercel/fetch": "^6.2.0",
"stripe": "^8.197.0",
"lodash.debounce": "^4.0.8",
"node-fetch": "^2.6.7",
"cookie": "^0.4.1"
},
"peerDependencies": {

View File

@@ -1,72 +1,65 @@
import type { CartEndpoint } from '.'
import type { RawVariant } from '../../../types/product'
import type { LineItem } from '@vercel/commerce/types/cart'
import { serialize } from 'cookie'
import type { RawVariantSpec } from '../../../types/product'
import { formatCart } from '../../utils/cart'
import { serialize } from 'cookie'
const addItem: CartEndpoint['handlers']['addItem'] = async ({
res,
req,
body: { cartId, item },
config: { restBuyerFetch, cartCookie, tokenCookie },
}) => {
// Return an error if no item is present
if (!item) {
return res.status(400).json({
data: null,
errors: [{ message: 'Missing item' }],
})
}
// Store token
let token
// Set the quantity if not present
if (!item.quantity) item.quantity = 1
// Get token
let token = req.cookies.get(tokenCookie)
let headers: any = {}
// Create an order if it doesn't exist
if (!cartId) {
const { ID, meta } = await restBuyerFetch(
'POST',
`/orders/Outgoing`,
{}
).then((response: { ID: string; meta: { token: string } }) => response)
{},
{ token }
)
// Set the cart id and token
cartId = ID
token = meta.token
// Set the cart and token cookie
res.setHeader('Set-Cookie', [
serialize(tokenCookie, meta.token, {
maxAge: 60 * 60 * 24 * 30,
expires: new Date(Date.now() + 60 * 60 * 24 * 30 * 1000),
secure: process.env.NODE_ENV === 'production',
path: '/',
sameSite: 'lax',
}),
serialize(cartCookie, cartId, {
maxAge: 60 * 60 * 24 * 30,
expires: new Date(Date.now() + 60 * 60 * 24 * 30 * 1000),
secure: process.env.NODE_ENV === 'production',
path: '/',
sameSite: 'lax',
}),
])
headers = {
'set-cookie': [
serialize(cartCookie, cartId!, {
maxAge: 60 * 60 * 24 * 30,
expires: new Date(Date.now() + 60 * 60 * 24 * 30 * 1000),
secure: process.env.NODE_ENV === 'production',
path: '/',
sameSite: 'lax',
}),
],
}
if (meta?.token) {
headers['set-cookie'].push(
serialize(tokenCookie, meta.token?.access_token, {
maxAge: meta.token.expires_in,
expires: new Date(Date.now() + meta.token.expires_in * 1000),
secure: process.env.NODE_ENV === 'production',
path: '/',
sameSite: 'lax',
})
)
}
}
// Store specs
let specs: RawVariant['Specs'] = []
let specs: RawVariantSpec[] = []
// If a variant is present, fetch its specs
if (item.variantId) {
specs = await restBuyerFetch(
if (item.variantId !== 'undefined') {
const { Specs } = await restBuyerFetch(
'GET',
`/me/products/${item.productId}/variants/${item.variantId}`,
null,
{ token }
).then((res: RawVariant) => res.Specs)
)
specs = Specs
}
// Add the item to the order
@@ -81,19 +74,19 @@ const addItem: CartEndpoint['handlers']['addItem'] = async ({
{ token }
)
// Get cart
const [cart, lineItems] = await Promise.all([
// Get cart & line items
const [cart, { Items }] = await Promise.all([
restBuyerFetch('GET', `/orders/Outgoing/${cartId}`, null, { token }),
restBuyerFetch('GET', `/orders/Outgoing/${cartId}/lineitems`, null, {
token,
}).then((response: { Items: LineItem[] }) => response.Items),
}),
])
// Format cart
const formattedCart = formatCart(cart, lineItems)
const formattedCart = formatCart(cart, Items)
// Return cart and errors
res.status(200).json({ data: formattedCart, errors: [] })
// Return cart and headers
return { data: formattedCart, headers }
}
export default addItem

View File

@@ -1,64 +1,56 @@
import type { OrdercloudLineItem } from '../../../types/cart'
import type { CartEndpoint } from '.'
import { serialize } from 'cookie'
import { formatCart } from '../../utils/cart'
// Return current cart info
const getCart: CartEndpoint['handlers']['getCart'] = async ({
req,
res,
body: { cartId },
config: { restBuyerFetch, cartCookie, tokenCookie },
}) => {
// If no cartId is provided, return data null
if (!cartId) {
return res.status(400).json({
data: null,
errors: [{ message: 'Invalid request' }],
})
return { data: null }
}
try {
// Get token from cookies
const token = req.cookies[tokenCookie]
// Get token
const token = req.cookies.get(tokenCookie)
// Get cart
const cart = await restBuyerFetch(
'GET',
`/orders/Outgoing/${cartId}`,
null,
{ token }
)
// Get line items
const lineItems = await restBuyerFetch(
'GET',
`/orders/Outgoing/${cartId}/lineitems`,
null,
{ token }
).then((response: { Items: OrdercloudLineItem[] }) => response.Items)
// Format cart
const formattedCart = formatCart(cart, lineItems)
// Return cart and errors
res.status(200).json({ data: formattedCart, errors: [] })
} catch (error) {
// Reset cart and token cookie
res.setHeader('Set-Cookie', [
serialize(cartCookie, cartId, {
maxAge: -1,
path: '/',
}),
serialize(tokenCookie, cartId, {
maxAge: -1,
path: '/',
// Get cart & line items
const [cart, { Items }] = await Promise.all([
restBuyerFetch('GET', `/orders/Outgoing/${cartId}`, null, { token }),
restBuyerFetch('GET', `/orders/Outgoing/${cartId}/lineitems`, null, {
token,
}),
])
// Format cart
const formattedCart = formatCart(cart, Items)
// Return cart and errors
return {
data: formattedCart,
}
} catch (error) {
console.error(error)
const headers = {
'set-cookie': [
serialize(cartCookie, '', {
maxAge: -1,
path: '/',
}),
serialize(tokenCookie, '', {
maxAge: -1,
path: '/',
}),
],
}
// Return empty cart
res.status(200).json({ data: null, errors: [] })
return {
data: null,
headers,
}
}
}

View File

@@ -1,5 +1,5 @@
import type { CartSchema } from '@vercel/commerce/types/cart'
import type { OrdercloudAPI } from '../..'
import type { CartSchema } from '@vercel/commerce/types/cart'
import { GetAPISchema, createEndpoint } from '@vercel/commerce/api'
import cartEndpoint from '@vercel/commerce/api/endpoints/cart'
@@ -9,9 +9,8 @@ import addItem from './add-item'
import updateItem from './update-item'
import removeItem from './remove-item'
export type CartAPI = GetAPISchema<OrdercloudAPI, CartSchema>
export type CartEndpoint = CartAPI['endpoint']
export type CartAPI = GetAPISchema<OrdercloudAPI, CartSchema>
export const handlers: CartEndpoint['handlers'] = {
getCart,

View File

@@ -1,30 +1,22 @@
import type { CartEndpoint } from '.'
import { formatCart } from '../../utils/cart'
import { OrdercloudLineItem } from '../../../types/cart'
const removeItem: CartEndpoint['handlers']['removeItem'] = async ({
req,
res,
body: { cartId, itemId },
config: { restBuyerFetch, tokenCookie },
}) => {
if (!cartId || !itemId) {
return res.status(400).json({
data: null,
errors: [{ message: 'Invalid request' }],
})
}
// Get token from cookies
const token = req.cookies[tokenCookie]
const token = req.cookies.get(tokenCookie)
// Remove the item to the order
await restBuyerFetch(
'DELETE',
`/orders/Outgoing/${cartId}/lineitems/${itemId}`,
null,
{ token }
{
token,
}
)
// Get cart
@@ -39,7 +31,7 @@ const removeItem: CartEndpoint['handlers']['removeItem'] = async ({
const formattedCart = formatCart(cart, lineItems)
// Return cart and errors
res.status(200).json({ data: formattedCart, errors: [] })
return { data: formattedCart }
}
export default removeItem

View File

@@ -6,19 +6,10 @@ import { formatCart } from '../../utils/cart'
const updateItem: CartEndpoint['handlers']['updateItem'] = async ({
req,
res,
body: { cartId, itemId, item },
config: { restBuyerFetch, tokenCookie },
}) => {
if (!cartId || !itemId || !item) {
return res.status(400).json({
data: null,
errors: [{ message: 'Invalid request' }],
})
}
// Get token from cookies
const token = req.cookies[tokenCookie]
const token = req.cookies.get(tokenCookie)
// Store specs
let specs: RawVariant['Specs'] = []
@@ -27,9 +18,7 @@ const updateItem: CartEndpoint['handlers']['updateItem'] = async ({
if (item.variantId) {
specs = await restBuyerFetch(
'GET',
`/me/products/${item.productId}/variants/${item.variantId}`,
null,
{ token }
`/me/products/${item.productId}/variants/${item.variantId}`
).then((res: RawVariant) => res.Specs)
}
@@ -42,7 +31,9 @@ const updateItem: CartEndpoint['handlers']['updateItem'] = async ({
Quantity: item.quantity,
Specs: specs,
},
{ token }
{
token,
}
)
// Get cart
@@ -57,7 +48,7 @@ const updateItem: CartEndpoint['handlers']['updateItem'] = async ({
const formattedCart = formatCart(cart, lineItems)
// Return cart and errors
res.status(200).json({ data: formattedCart, errors: [] })
return { data: formattedCart }
}
export default updateItem

View File

@@ -1,13 +1,14 @@
import { normalize as normalizeProduct } from '../../../../utils/product'
import { ProductsEndpoint } from '.'
import { normalize as normalizeProduct } from '../../../../utils/product'
// Get products for the product list page. Search and category filter implemented. Sort and brand filter not implemented.
const getProducts: ProductsEndpoint['handlers']['getProducts'] = async ({
req,
res,
body: { search, categoryId, brandId, sort },
config: { restBuyerFetch, cartCookie, tokenCookie },
body: { search, categoryId },
config: { restBuyerFetch, tokenCookie },
}) => {
const token = req.cookies.get(tokenCookie)
//Use a dummy base as we only care about the relative path
const url = new URL('/me/products', 'http://a')
@@ -18,20 +19,19 @@ const getProducts: ProductsEndpoint['handlers']['getProducts'] = async ({
url.searchParams.set('categoryID', String(categoryId))
}
// Get token from cookies
const token = req.cookies[tokenCookie]
var rawProducts = await restBuyerFetch(
'GET',
url.pathname + url.search,
null,
{ token }
)
).then((response: { Items: any[] }) => response.Items)
const products = rawProducts.Items.map(normalizeProduct)
const found = rawProducts?.Items?.length > 0
res.status(200).json({ data: { products, found } })
return {
data: {
products: rawProducts.map(normalizeProduct),
found: rawProducts?.length > 0,
},
}
}
export default getProducts

View File

@@ -2,27 +2,15 @@ import type { CheckoutEndpoint } from '.'
const getCheckout: CheckoutEndpoint['handlers']['getCheckout'] = async ({
req,
res,
body: { cartId },
config: { restBuyerFetch, tokenCookie },
config: { restBuyerFetch },
}) => {
// Return an error if no item is present
if (!cartId) {
return res.status(400).json({
data: null,
errors: [{ message: 'Missing cookie' }],
})
}
// Get token from cookies
const token = req.cookies[tokenCookie]
const token = req.cookies.get('token')
// Register credit card
const payments = await restBuyerFetch(
'GET',
`/orders/Outgoing/${cartId}/payments`,
null,
{ token }
`/orders/Outgoing/${cartId}/payments`
).then((response: { Items: unknown[] }) => response.Items)
const address = await restBuyerFetch(
@@ -35,15 +23,15 @@ const getCheckout: CheckoutEndpoint['handlers']['getCheckout'] = async ({
)
// Return cart and errors
res.status(200).json({
return {
data: {
hasPayment: payments.length > 0,
hasShipping: Boolean(address),
addressId: address,
cardId: payments[0]?.ID,
},
errors: [],
})
}
}
export default getCheckout

View File

@@ -2,31 +2,18 @@ import type { CheckoutEndpoint } from '.'
const submitCheckout: CheckoutEndpoint['handlers']['submitCheckout'] = async ({
req,
res,
body: { cartId },
config: { restBuyerFetch, tokenCookie },
}) => {
// Return an error if no item is present
if (!cartId) {
return res.status(400).json({
data: null,
errors: [{ message: 'Missing item' }],
})
}
// Get token from cookies
const token = req.cookies[tokenCookie]
const token = req.cookies.get(tokenCookie)
// Submit order
await restBuyerFetch(
'POST',
`/orders/Outgoing/${cartId}/submit`,
{},
{ token }
)
await restBuyerFetch('POST', `/orders/Outgoing/${cartId}/submit`, null, {
token,
})
// Return cart and errors
res.status(200).json({ data: null, errors: [] })
return { data: null }
}
export default submitCheckout

View File

@@ -1,25 +1,11 @@
import type { CustomerAddressEndpoint } from '.'
const addItem: CustomerAddressEndpoint['handlers']['addItem'] = async ({
res,
req,
body: { item, cartId },
config: { restBuyerFetch },
config: { restBuyerFetch, tokenCookie },
}) => {
// Return an error if no item is present
if (!item) {
return res.status(400).json({
data: null,
errors: [{ message: 'Missing item' }],
})
}
// Return an error if no item is present
if (!cartId) {
return res.status(400).json({
data: null,
errors: [{ message: 'Cookie not found' }],
})
}
const token = req.cookies.get(tokenCookie)
// Register address
const address = await restBuyerFetch('POST', `/me/addresses`, {
@@ -37,11 +23,16 @@ const addItem: CustomerAddressEndpoint['handlers']['addItem'] = async ({
}).then((response: { ID: string }) => response.ID)
// Assign address to order
await restBuyerFetch('PATCH', `/orders/Outgoing/${cartId}`, {
ShippingAddressID: address,
})
await restBuyerFetch(
'PATCH',
`/orders/Outgoing/${cartId}`,
{
ShippingAddressID: address,
},
{ token }
)
return res.status(200).json({ data: null, errors: [] })
return { data: null }
}
export default addItem

View File

@@ -1,9 +1,8 @@
import type { CustomerAddressEndpoint } from '.'
const getCards: CustomerAddressEndpoint['handlers']['getAddresses'] = async ({
res,
}) => {
return res.status(200).json({ data: null, errors: [] })
}
const getAddresses: CustomerAddressEndpoint['handlers']['getAddresses'] =
() => {
return Promise.resolve({ data: null })
}
export default getCards
export default getAddresses

View File

@@ -1,9 +1,7 @@
import type { CustomerAddressEndpoint } from '.'
const removeItem: CustomerAddressEndpoint['handlers']['removeItem'] = async ({
res,
}) => {
return res.status(200).json({ data: null, errors: [] })
const removeItem: CustomerAddressEndpoint['handlers']['removeItem'] = () => {
return Promise.resolve({ data: null })
}
export default removeItem

View File

@@ -1,9 +1,7 @@
import type { CustomerAddressEndpoint } from '.'
const updateItem: CustomerAddressEndpoint['handlers']['updateItem'] = async ({
res,
}) => {
return res.status(200).json({ data: null, errors: [] })
const updateItem: CustomerAddressEndpoint['handlers']['updateItem'] = () => {
return Promise.resolve({ data: null })
}
export default updateItem

View File

@@ -1,53 +1,47 @@
import type { CustomerCardEndpoint } from '.'
import type { OredercloudCreditCard } from '../../../../types/customer/card'
import Stripe from 'stripe'
const stripe = new Stripe(process.env.STRIPE_SECRET as string, {
apiVersion: '2020-08-27',
})
const addItem: CustomerCardEndpoint['handlers']['addItem'] = async ({
res,
req,
body: { item, cartId },
config: { restBuyerFetch, restMiddlewareFetch },
config: { restBuyerFetch, tokenCookie },
}) => {
// Return an error if no item is present
if (!item) {
return res.status(400).json({
data: null,
errors: [{ message: 'Missing item' }],
})
}
// Return an error if no item is present
if (!cartId) {
return res.status(400).json({
data: null,
errors: [{ message: 'Cookie not found' }],
})
}
// Get token
const token = await stripe.tokens
.create({
const token = req.cookies.get(tokenCookie)
const [exp_month, exp_year] = item.cardExpireDate.split('/')
const stripeToken = await fetch('https://api.stripe.com/v1/tokens', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.STRIPE_SECRET}`,
},
body: JSON.stringify({
card: {
number: item.cardNumber,
exp_month: item.cardExpireDate.split('/')[0],
exp_year: item.cardExpireDate.split('/')[1],
exp_month,
exp_year,
cvc: item.cardCvc,
},
})
.then((res: { id: string }) => res.id)
}),
})
.then((res) => res.json())
.then((res) => res.id)
// Register credit card
const creditCard = await restBuyerFetch('POST', `/me/creditcards`, {
Token: token,
CardType: 'credit',
PartialAccountNumber: item.cardNumber.slice(-4),
CardholderName: item.cardHolder,
ExpirationDate: item.cardExpireDate,
}).then((response: OredercloudCreditCard) => response.ID)
const creditCard = await restBuyerFetch(
'POST',
`/me/creditcards`,
{
Token: stripeToken,
CardType: 'credit',
PartialAccountNumber: item.cardNumber.slice(-4),
CardholderName: item.cardHolder,
ExpirationDate: item.cardExpireDate,
},
{
token,
}
).then((response: OredercloudCreditCard) => response.ID)
// Assign payment to order
const payment = await restBuyerFetch(
@@ -56,19 +50,18 @@ const addItem: CustomerCardEndpoint['handlers']['addItem'] = async ({
{
Type: 'CreditCard',
CreditCardID: creditCard,
},
{
token,
}
).then((response: { ID: string }) => response.ID)
// Accept payment to order
await restMiddlewareFetch(
'PATCH',
`/orders/All/${cartId}/payments/${payment}`,
{
Accepted: true,
}
)
await restBuyerFetch('PATCH', `/orders/All/${cartId}/payments/${payment}`, {
Accepted: true,
})
return res.status(200).json({ data: null, errors: [] })
return { data: null }
}
export default addItem

View File

@@ -1,9 +1,7 @@
import type { CustomerCardEndpoint } from '.'
const getCards: CustomerCardEndpoint['handlers']['getCards'] = async ({
res,
}) => {
return res.status(200).json({ data: null, errors: [] })
const getCards: CustomerCardEndpoint['handlers']['getCards'] = () => {
return Promise.resolve({ data: null })
}
export default getCards

View File

@@ -1,9 +1,7 @@
import type { CustomerCardEndpoint } from '.'
const removeItem: CustomerCardEndpoint['handlers']['removeItem'] = async ({
res,
}) => {
return res.status(200).json({ data: null, errors: [] })
const removeItem: CustomerCardEndpoint['handlers']['removeItem'] = () => {
return Promise.resolve({ data: null })
}
export default removeItem

View File

@@ -1,9 +1,7 @@
import type { CustomerCardEndpoint } from '.'
const updateItem: CustomerCardEndpoint['handlers']['updateItem'] = async ({
res,
}) => {
return res.status(200).json({ data: null, errors: [] })
const updateItem: CustomerCardEndpoint['handlers']['updateItem'] = () => {
return Promise.resolve({ data: null })
}
export default updateItem

View File

@@ -1 +0,0 @@
export default function noopApi(...args: any[]): void {}

View File

@@ -0,0 +1,21 @@
import type { OrdercloudAPI } from '..'
import createEndpoints from '@vercel/commerce/api/endpoints'
import cart from './cart'
import checkout from './checkout'
import products from './catalog/products'
import customerCard from './customer/card'
import customerAddress from './customer/address'
const endpoints = {
cart,
checkout,
'customer/card': customerCard,
'customer/address': customerAddress,
'catalog/products': products,
}
export default function ordercloudAPI(commerce: OrdercloudAPI) {
return createEndpoints(commerce, endpoints)
}

View File

@@ -1 +0,0 @@
export default function noopApi(...args: any[]): void {}

View File

@@ -1 +0,0 @@
export default function noopApi(...args: any[]): void {}

View File

@@ -1 +0,0 @@
export default function noopApi(...args: any[]): void {}

View File

@@ -1 +0,0 @@
export default function noopApi(...args: any[]): void {}

View File

@@ -1,6 +1,6 @@
import type { CommerceAPI, CommerceAPIConfig } from '@vercel/commerce/api'
import { getCommerceApi as commerceApi } from '@vercel/commerce/api'
import { createBuyerFetcher, createMiddlewareFetcher } from './utils/fetch-rest'
import { createBuyerFetcher } from './utils/fetch-rest'
import createGraphqlFetcher from './utils/fetch-graphql'
import getAllPages from './operations/get-all-pages'
@@ -25,12 +25,6 @@ export interface OrdercloudConfig extends CommerceAPIConfig {
body?: Record<string, unknown>,
fetchOptions?: Record<string, any>
) => Promise<T>
restMiddlewareFetch: <T>(
method: string,
resource: string,
body?: Record<string, unknown>,
fetchOptions?: Record<string, any>
) => Promise<T>
apiVersion: string
tokenCookie: string
}
@@ -44,9 +38,6 @@ const config: OrdercloudConfig = {
tokenCookie: TOKEN_COOKIE,
cartCookieMaxAge: 2592000,
restBuyerFetch: createBuyerFetcher(() => getCommerceApi().getConfig()),
restMiddlewareFetch: createMiddlewareFetcher(() =>
getCommerceApi().getConfig()
),
fetch: createGraphqlFetcher(() => getCommerceApi().getConfig()),
}

View File

@@ -21,7 +21,7 @@ export function formatCart(
name: lineItem.Product.Name,
quantity: lineItem.Quantity,
discounts: [],
path: lineItem.ProductID,
path: `/product/${lineItem.ProductID}`,
variant: {
id: lineItem.Variant ? String(lineItem.Variant.ID) : '',
sku: lineItem.ID,

View File

@@ -1,11 +1,7 @@
import vercelFetch from '@vercel/fetch'
import { FetcherError } from '@vercel/commerce/utils/errors'
import { CustomNodeJsGlobal } from '../../types/node';
import { OrdercloudConfig } from '../index'
// Get an instance to vercel fetch
const fetch = vercelFetch()
export let token: string | null = null
// Get token util
async function getToken({
@@ -16,7 +12,12 @@ async function getToken({
baseUrl: string
clientId: string
clientSecret?: string
}): Promise<string> {
}): Promise<{
access_token: string
expires_in: number
refresh_token: string
token_type: string
}> {
// If not, get a new one and store it
const authResponse = await fetch(`${baseUrl}/oauth/token`, {
method: 'POST',
@@ -32,6 +33,8 @@ async function getToken({
// Get the body of it
const error = await authResponse.json()
console.log(JSON.stringify(error, null, 2))
// And return an error
throw new FetcherError({
errors: [{ message: error.error_description.Code }],
@@ -40,9 +43,7 @@ async function getToken({
}
// Return the token
return authResponse
.json()
.then((response: { access_token: string }) => response.access_token)
return authResponse.json()
}
export async function fetchData<T>(opts: {
@@ -74,12 +75,18 @@ export async function fetchData<T>(opts: {
// If something failed getting the data response
if (!dataResponse.ok) {
// Get the body of it
const error = await dataResponse.textConverted()
let errors
// And return an error
try {
// Get the body of it
const error = await dataResponse.json()
errors = error.Errors
} catch (e) {
const message = await dataResponse.text()
errors = [{ message }]
}
throw new FetcherError({
errors: [{ message: error || dataResponse.statusText }],
errors,
status: dataResponse.status,
})
}
@@ -93,42 +100,6 @@ export async function fetchData<T>(opts: {
}
}
export const createMiddlewareFetcher: (
getConfig: () => OrdercloudConfig
) => <T>(
method: string,
path: string,
body?: Record<string, unknown>,
fetchOptions?: Record<string, any>
) => Promise<T> =
(getConfig) =>
async <T>(
method: string,
path: string,
body?: Record<string, unknown>,
fetchOptions?: Record<string, any>
) => {
// Get provider config
const config = getConfig()
// Get a token
const token = await getToken({
baseUrl: config.commerceUrl,
clientId: process.env.ORDERCLOUD_MIDDLEWARE_CLIENT_ID as string,
clientSecret: process.env.ORDERCLOUD_MIDDLEWARE_CLIENT_SECRET,
})
// Return the data and specify the expected type
return fetchData<T>({
token,
fetchOptions,
method,
config,
path,
body,
})
}
export const createBuyerFetcher: (
getConfig: () => OrdercloudConfig
) => <T>(
@@ -144,28 +115,27 @@ export const createBuyerFetcher: (
body?: Record<string, unknown>,
fetchOptions?: Record<string, any>
) => {
const customGlobal = global as unknown as CustomNodeJsGlobal;
if (fetchOptions?.token) {
token = fetchOptions?.token
}
// Get provider config
const config = getConfig()
let meta: any = {}
// If a token was passed, set it on global
if (fetchOptions?.token) {
customGlobal.token = fetchOptions.token
}
// Get a token
if (!customGlobal.token) {
customGlobal.token = await getToken({
if (!token) {
const newToken = await getToken({
baseUrl: config.commerceUrl,
clientId: process.env.ORDERCLOUD_BUYER_CLIENT_ID as string,
})
token = newToken.access_token
meta.token = newToken
}
// Return the data and specify the expected type
const data = await fetchData<T>({
token: customGlobal.token as string,
token,
fetchOptions,
config,
method,
@@ -175,6 +145,6 @@ export const createBuyerFetcher: (
return {
...data,
meta: { token: customGlobal.token as string },
meta,
}
}

View File

@@ -10,7 +10,7 @@ export default useAddItem as UseAddItem<typeof handler>
export const handler: MutationHook<AddItemHook> = {
fetchOptions: {
url: '/api/cart',
url: '/api/commerce/cart',
method: 'POST',
},
async fetcher({ input: item, options, fetch }) {

View File

@@ -8,7 +8,7 @@ export default useCart as UseCart<typeof handler>
export const handler: SWRHook<GetCartHook> = {
fetchOptions: {
url: '/api/cart',
url: '/api/commerce/cart',
method: 'GET',
},
useHook: ({ useData }) =>

View File

@@ -2,12 +2,18 @@ import type {
MutationHookContext,
HookFetcherContext,
} from '@vercel/commerce/utils/types'
import type { Cart, LineItem, RemoveItemHook } from '@vercel/commerce/types/cart'
import type {
Cart,
LineItem,
RemoveItemHook,
} from '@vercel/commerce/types/cart'
import { useCallback } from 'react'
import { ValidationError } from '@vercel/commerce/utils/errors'
import useRemoveItem, { UseRemoveItem } from '@vercel/commerce/cart/use-remove-item'
import useRemoveItem, {
UseRemoveItem,
} from '@vercel/commerce/cart/use-remove-item'
import useCart from './use-cart'
@@ -23,7 +29,7 @@ export default useRemoveItem as UseRemoveItem<typeof handler>
export const handler = {
fetchOptions: {
url: '/api/cart',
url: '/api/commerce/cart',
method: 'DELETE',
},
async fetcher({

View File

@@ -9,7 +9,9 @@ import debounce from 'lodash.debounce'
import { MutationHook } from '@vercel/commerce/utils/types'
import { ValidationError } from '@vercel/commerce/utils/errors'
import useUpdateItem, { UseUpdateItem } from '@vercel/commerce/cart/use-update-item'
import useUpdateItem, {
UseUpdateItem,
} from '@vercel/commerce/cart/use-update-item'
import { handler as removeItemHandler } from './use-remove-item'
import useCart from './use-cart'
@@ -22,7 +24,7 @@ export default useUpdateItem as UseUpdateItem<any>
export const handler: MutationHook<any> = {
fetchOptions: {
url: '/api/cart',
url: '/api/commerce/cart',
method: 'PUT',
},
async fetcher({

View File

@@ -11,7 +11,7 @@ export default useCheckout as UseCheckout<typeof handler>
export const handler: SWRHook<GetCheckoutHook> = {
fetchOptions: {
url: '/api/checkout',
url: '/api/commerce/checkout',
method: 'GET',
},
useHook: ({ useData }) =>

View File

@@ -10,7 +10,7 @@ export default useSubmitCheckout as UseSubmitCheckout<typeof handler>
export const handler: MutationHook<SubmitCheckoutHook> = {
fetchOptions: {
url: '/api/checkout',
url: '/api/commerce/checkout',
method: 'POST',
},
async fetcher({ input: item, options, fetch }) {

View File

@@ -2,14 +2,16 @@ import type { AddItemHook } from '@vercel/commerce/types/customer/address'
import type { MutationHook } from '@vercel/commerce/utils/types'
import { useCallback } from 'react'
import useAddItem, { UseAddItem } from '@vercel/commerce/customer/address/use-add-item'
import useAddItem, {
UseAddItem,
} from '@vercel/commerce/customer/address/use-add-item'
import useAddresses from './use-addresses'
export default useAddItem as UseAddItem<typeof handler>
export const handler: MutationHook<AddItemHook> = {
fetchOptions: {
url: '/api/customer/address',
url: '/api/commerce/customer/address',
method: 'POST',
},
async fetcher({ input: item, options, fetch }) {
@@ -27,9 +29,7 @@ export const handler: MutationHook<AddItemHook> = {
return useCallback(
async function addItem(input) {
const data = await fetch({ input })
await mutate([data], false)
await mutate(data ? [data] : [], false)
return data
},
[fetch, mutate]

View File

@@ -10,7 +10,7 @@ export default useAddresses as UseAddresses<typeof handler>
export const handler: SWRHook<GetAddressesHook> = {
fetchOptions: {
url: '/api/customer/address',
url: '/api/commerce/customer/address',
method: 'GET',
},
useHook: ({ useData }) =>

View File

@@ -2,7 +2,10 @@ import type {
MutationHookContext,
HookFetcherContext,
} from '@vercel/commerce/utils/types'
import type { Address, RemoveItemHook } from '@vercel/commerce/types/customer/address'
import type {
Address,
RemoveItemHook,
} from '@vercel/commerce/types/customer/address'
import { useCallback } from 'react'
@@ -14,7 +17,7 @@ import useRemoveItem, {
import useAddresses from './use-addresses'
export type RemoveItemFn<T = any> = T extends Address
? (input?: RemoveItemActionInput<T>) => Promise<Address | null | undefined>
? (input?: RemoveItemActionInput<T>) => Promise<Address | null>
: (input: RemoveItemActionInput<T>) => Promise<Address | null>
export type RemoveItemActionInput<T = any> = T extends Address
@@ -25,7 +28,7 @@ export default useRemoveItem as UseRemoveItem<typeof handler>
export const handler = {
fetchOptions: {
url: '/api/customer/address',
url: '/api/commerce/customer/address',
method: 'DELETE',
},
async fetcher({

View File

@@ -2,7 +2,10 @@ import type {
HookFetcherContext,
MutationHookContext,
} from '@vercel/commerce/utils/types'
import type { UpdateItemHook, Address } from '@vercel/commerce/types/customer/address'
import type {
UpdateItemHook,
Address,
} from '@vercel/commerce/types/customer/address'
import { useCallback } from 'react'
@@ -21,7 +24,7 @@ export default useUpdateItem as UseUpdateItem<any>
export const handler: MutationHook<any> = {
fetchOptions: {
url: '/api/customer/address',
url: '/api/commerce/customer/address',
method: 'PUT',
},
async fetcher({

View File

@@ -2,14 +2,16 @@ import type { AddItemHook } from '@vercel/commerce/types/customer/card'
import type { MutationHook } from '@vercel/commerce/utils/types'
import { useCallback } from 'react'
import useAddItem, { UseAddItem } from '@vercel/commerce/customer/card/use-add-item'
import useAddItem, {
UseAddItem,
} from '@vercel/commerce/customer/card/use-add-item'
import useCards from './use-cards'
export default useAddItem as UseAddItem<typeof handler>
export const handler: MutationHook<AddItemHook> = {
fetchOptions: {
url: '/api/customer/card',
url: '/api/commerce/customer/card',
method: 'POST',
},
async fetcher({ input: item, options, fetch }) {
@@ -28,7 +30,7 @@ export const handler: MutationHook<AddItemHook> = {
async function addItem(input) {
const data = await fetch({ input })
await mutate([data], false)
await mutate(data ? [data] : [], false)
return data
},

View File

@@ -8,7 +8,7 @@ export default useCard as UseCards<typeof handler>
export const handler: SWRHook<GetCardsHook> = {
fetchOptions: {
url: '/api/customer/card',
url: '/api/commerce/customer/card',
method: 'GET',
},
useHook: ({ useData }) =>

View File

@@ -14,7 +14,7 @@ import useRemoveItem, {
import useCards from './use-cards'
export type RemoveItemFn<T = any> = T extends Card
? (input?: RemoveItemActionInput<T>) => Promise<Card | null | undefined>
? (input?: RemoveItemActionInput<T>) => Promise<Card | null>
: (input: RemoveItemActionInput<T>) => Promise<Card | null>
export type RemoveItemActionInput<T = any> = T extends Card
@@ -25,7 +25,7 @@ export default useRemoveItem as UseRemoveItem<typeof handler>
export const handler = {
fetchOptions: {
url: '/api/customer/card',
url: '/api/commerce/customer/card',
method: 'DELETE',
},
async fetcher({

View File

@@ -21,7 +21,7 @@ export default useUpdateItem as UseUpdateItem<any>
export const handler: MutationHook<any> = {
fetchOptions: {
url: '/api/customer/card',
url: '/api/commerce/customer/card',
method: 'PUT',
},
async fetcher({

View File

@@ -5,7 +5,7 @@ export default useSearch as UseSearch<typeof handler>
export const handler: SWRHook<SearchProductsHook> = {
fetchOptions: {
url: '/api/catalog/products',
url: '/api/commerce/catalog/products',
method: 'GET',
},
fetcher({ input: { search, categoryId, brandId, sort }, options, fetch }) {

View File

@@ -1,4 +1,4 @@
interface RawVariantSpec {
export interface RawVariantSpec {
SpecID: string
Name: string
OptionID: string