Save token to cookie

This commit is contained in:
goncy
2021-09-30 14:56:04 -03:00
parent efa1521bb9
commit 4ab3cf5214
15 changed files with 274 additions and 161 deletions

View File

@@ -9,7 +9,7 @@ import { formatCart } from '../../utils/cart'
const addItem: CartEndpoint['handlers']['addItem'] = async ({
res,
body: { cartId, item },
config: { restFetch, cartCookie },
config: { restBuyerFetch, cartCookie, tokenCookie },
}) => {
// Return an error if no item is present
if (!item) {
@@ -19,52 +19,74 @@ const addItem: CartEndpoint['handlers']['addItem'] = async ({
})
}
// Store token
let token
// Set the quantity if not present
if (!item.quantity) item.quantity = 1
// Create an order if it doesn't exist
if (!cartId) {
cartId = await restFetch('POST', `/orders/Outgoing`, {}).then(
(response: { ID: string }) => response.ID
)
}
const { ID, meta } = await restBuyerFetch(
'POST',
`/orders/Outgoing`,
{}
).then((response: { ID: string; meta: { token: string } }) => response)
// Set the cart cookie
res.setHeader(
'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',
})
)
// 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',
}),
])
}
// Store specs
let specs: RawVariant['Specs'] = []
// If a variant is present, fetch its specs
if (item.variantId) {
specs = await restFetch(
specs = await restBuyerFetch(
'GET',
`/me/products/${item.productId}/variants/${item.variantId}`
`/me/products/${item.productId}/variants/${item.variantId}`,
null,
{ token }
).then((res: RawVariant) => res.Specs)
}
// Add the item to the order
await restFetch('POST', `/orders/Outgoing/${cartId}/lineitems`, {
ProductID: item.productId,
Quantity: item.quantity,
Specs: specs,
})
await restBuyerFetch(
'POST',
`/orders/Outgoing/${cartId}/lineitems`,
{
ProductID: item.productId,
Quantity: item.quantity,
Specs: specs,
},
{ token }
)
// Get cart
const [cart, lineItems] = await Promise.all([
restFetch('GET', `/orders/Outgoing/${cartId}`),
restFetch('GET', `/orders/Outgoing/${cartId}/lineitems`).then(
(response: { Items: OrdercloudLineItem[] }) => response.Items
),
restBuyerFetch('GET', `/orders/Outgoing/${cartId}`, null, { token }),
restBuyerFetch('GET', `/orders/Outgoing/${cartId}/lineitems`, null, {
token,
}).then((response: { Items: OrdercloudLineItem[] }) => response.Items),
])
// Format cart

View File

@@ -7,9 +7,10 @@ import { formatCart } from '../../utils/cart'
// Return current cart info
const getCart: CartEndpoint['handlers']['getCart'] = async ({
req,
res,
body: { cartId },
config: { restFetch, cartCookie },
config: { restBuyerFetch, cartCookie, tokenCookie },
}) => {
if (!cartId) {
return res.status(400).json({
@@ -19,13 +20,23 @@ const getCart: CartEndpoint['handlers']['getCart'] = async ({
}
try {
// Get token from cookies
const token = req.cookies[tokenCookie]
// Get cart
const cart = await restFetch('GET', `/orders/Outgoing/${cartId}`)
const cart = await restBuyerFetch(
'GET',
`/orders/Outgoing/${cartId}`,
null,
{ token }
)
// Get line items
const lineItems = await restFetch(
const lineItems = await restBuyerFetch(
'GET',
`/orders/Outgoing/${cartId}/lineitems`
`/orders/Outgoing/${cartId}/lineitems`,
null,
{ token }
).then((response: { Items: OrdercloudLineItem[] }) => response.Items)
// Format cart
@@ -34,14 +45,17 @@ const getCart: CartEndpoint['handlers']['getCart'] = async ({
// Return cart and errors
res.status(200).json({ data: formattedCart, errors: [] })
} catch (error) {
// Reset cart cookie
res.setHeader(
'Set-Cookie',
// Reset cart and token cookie
res.setHeader('Set-Cookie', [
serialize(cartCookie, cartId, {
maxAge: -1,
path: '/',
})
)
}),
serialize(tokenCookie, cartId, {
maxAge: -1,
path: '/',
}),
])
// Return empty cart
res.status(200).json({ data: null, errors: [] })

View File

@@ -4,9 +4,10 @@ import { formatCart } from '../../utils/cart'
import { OrdercloudLineItem } from '../../../types/cart'
const removeItem: CartEndpoint['handlers']['removeItem'] = async ({
req,
res,
body: { cartId, itemId },
config: { restFetch },
config: { restBuyerFetch, tokenCookie },
}) => {
if (!cartId || !itemId) {
return res.status(400).json({
@@ -15,18 +16,23 @@ const removeItem: CartEndpoint['handlers']['removeItem'] = async ({
})
}
// Get token from cookies
const token = req.cookies[tokenCookie]
// Remove the item to the order
await restFetch(
await restBuyerFetch(
'DELETE',
`/orders/Outgoing/${cartId}/lineitems/${itemId}`
`/orders/Outgoing/${cartId}/lineitems/${itemId}`,
null,
{ token }
)
// Get cart
const [cart, lineItems] = await Promise.all([
restFetch('GET', `/orders/Outgoing/${cartId}`),
restFetch('GET', `/orders/Outgoing/${cartId}/lineitems`).then(
(response: { Items: OrdercloudLineItem[] }) => response.Items
),
restBuyerFetch('GET', `/orders/Outgoing/${cartId}`, null, { token }),
restBuyerFetch('GET', `/orders/Outgoing/${cartId}/lineitems`, null, {
token,
}).then((response: { Items: OrdercloudLineItem[] }) => response.Items),
])
// Format cart

View File

@@ -5,9 +5,10 @@ import type { CartEndpoint } from '.'
import { formatCart } from '../../utils/cart'
const updateItem: CartEndpoint['handlers']['updateItem'] = async ({
req,
res,
body: { cartId, itemId, item },
config: { restFetch },
config: { restBuyerFetch, tokenCookie },
}) => {
if (!cartId || !itemId || !item) {
return res.status(400).json({
@@ -16,34 +17,40 @@ const updateItem: CartEndpoint['handlers']['updateItem'] = async ({
})
}
// Get token from cookies
const token = req.cookies[tokenCookie]
// Store specs
let specs: RawVariant['Specs'] = []
// If a variant is present, fetch its specs
if (item.variantId) {
specs = await restFetch(
specs = await restBuyerFetch(
'GET',
`/me/products/${item.productId}/variants/${item.variantId}`
`/me/products/${item.productId}/variants/${item.variantId}`,
null,
{ token }
).then((res: RawVariant) => res.Specs)
}
// Add the item to the order
await restFetch(
await restBuyerFetch(
'PATCH',
`/orders/Outgoing/${cartId}/lineitems/${itemId}`,
{
ProductID: item.productId,
Quantity: item.quantity,
Specs: specs,
}
},
{ token }
)
// Get cart
const [cart, lineItems] = await Promise.all([
restFetch('GET', `/orders/Outgoing/${cartId}`),
restFetch('GET', `/orders/Outgoing/${cartId}/lineitems`).then(
(response: { Items: OrdercloudLineItem[] }) => response.Items
),
restBuyerFetch('GET', `/orders/Outgoing/${cartId}`, null, { token }),
restBuyerFetch('GET', `/orders/Outgoing/${cartId}/lineitems`, null, {
token,
}).then((response: { Items: OrdercloudLineItem[] }) => response.Items),
])
// Format cart

View File

@@ -3,7 +3,7 @@ import type { CheckoutEndpoint } from '.'
const getCheckout: CheckoutEndpoint['handlers']['getCheckout'] = async ({
res,
body: { cartId },
config: { restFetch },
config: { restBuyerFetch },
}) => {
// Return an error if no item is present
if (!cartId) {
@@ -14,12 +14,15 @@ const getCheckout: CheckoutEndpoint['handlers']['getCheckout'] = async ({
}
// Register credit card
const payments = await restFetch(
const payments = await restBuyerFetch(
'GET',
`/orders/Outgoing/${cartId}/payments`
).then((response: { Items: unknown[] }) => response.Items)
const address = await restFetch('GET', `/orders/Outgoing/${cartId}`).then(
const address = await restBuyerFetch(
'GET',
`/orders/Outgoing/${cartId}`
).then(
(response: { ShippingAddressID: string }) => response.ShippingAddressID
)

View File

@@ -3,7 +3,7 @@ import type { CheckoutEndpoint } from '.'
const submitCheckout: CheckoutEndpoint['handlers']['submitCheckout'] = async ({
res,
body: { cartId },
config: { restFetch },
config: { restBuyerFetch },
}) => {
// Return an error if no item is present
if (!cartId) {
@@ -14,7 +14,7 @@ const submitCheckout: CheckoutEndpoint['handlers']['submitCheckout'] = async ({
}
// Submit order
await restFetch('POST', `/orders/Outgoing/${cartId}/submit`, {})
await restBuyerFetch('POST', `/orders/Outgoing/${cartId}/submit`, {})
// Return cart and errors
res.status(200).json({ data: null, errors: [] })

View File

@@ -3,7 +3,7 @@ import type { CustomerAddressEndpoint } from '.'
const addItem: CustomerAddressEndpoint['handlers']['addItem'] = async ({
res,
body: { item, cartId },
config: { restFetch },
config: { restBuyerFetch },
}) => {
// Return an error if no item is present
if (!item) {
@@ -22,25 +22,23 @@ const addItem: CustomerAddressEndpoint['handlers']['addItem'] = async ({
}
// Register address
const address = await restFetch('POST', `/me/addresses`, {
"AddressName": "main address",
"CompanyName": item.company,
"FirstName": item.firstName,
"LastName": item.lastName,
"Street1": item.streetNumber,
"Street2": item.streetNumber,
"City": item.city,
"State": item.city,
"Zip": item.zipCode,
"Country": item.country.slice(0, 2).toLowerCase(),
"Shipping": true
}).then(
(response: {ID: string}) => response.ID
)
const address = await restBuyerFetch('POST', `/me/addresses`, {
AddressName: 'main address',
CompanyName: item.company,
FirstName: item.firstName,
LastName: item.lastName,
Street1: item.streetNumber,
Street2: item.streetNumber,
City: item.city,
State: item.city,
Zip: item.zipCode,
Country: item.country.slice(0, 2).toLowerCase(),
Shipping: true,
}).then((response: { ID: string }) => response.ID)
// Assign address to order
await restFetch('PATCH', `/orders/Outgoing/${cartId}`, {
ShippingAddressID: address
await restBuyerFetch('PATCH', `/orders/Outgoing/${cartId}`, {
ShippingAddressID: address,
})
return res.status(200).json({ data: null, errors: [] })

View File

@@ -10,7 +10,7 @@ const stripe = new Stripe(process.env.STRIPE_SECRET as string, {
const addItem: CustomerCardEndpoint['handlers']['addItem'] = async ({
res,
body: { item, cartId },
config: { restFetch },
config: { restBuyerFetch, restMiddlewareFetch },
}) => {
// Return an error if no item is present
if (!item) {
@@ -41,7 +41,7 @@ const addItem: CustomerCardEndpoint['handlers']['addItem'] = async ({
.then((res: { id: string }) => res.id)
// Register credit card
const creditCard = await restFetch('POST', `/me/creditcards`, {
const creditCard = await restBuyerFetch('POST', `/me/creditcards`, {
Token: token,
CardType: 'credit',
PartialAccountNumber: item.cardNumber.slice(-4),
@@ -50,11 +50,23 @@ const addItem: CustomerCardEndpoint['handlers']['addItem'] = async ({
}).then((response: OredercloudCreditCard) => response.ID)
// Assign payment to order
await restFetch('POST', `/orders/Outgoing/${cartId}/payments`, {
Accepted: true,
Type: 'CreditCard',
CreditCardID: creditCard,
})
const payment = await restBuyerFetch(
'POST',
`/orders/All/${cartId}/payments`,
{
Type: 'CreditCard',
CreditCardID: creditCard,
}
).then((response: { ID: string }) => response.ID)
// Accept payment to order
await restMiddlewareFetch(
'PATCH',
`/orders/All/${cartId}/payments/${payment}`,
{
Accepted: true,
}
)
return res.status(200).json({ data: null, errors: [] })
}

View File

@@ -1,6 +1,6 @@
import type { CommerceAPI, CommerceAPIConfig } from '@commerce/api'
import { getCommerceApi as commerceApi } from '@commerce/api'
import createRestFetcher from './utils/fetch-rest'
import { createBuyerFetcher, createMiddlewareFetcher } from './utils/fetch-rest'
import createGraphqlFetcher from './utils/fetch-graphql'
import getAllPages from './operations/get-all-pages'
@@ -10,16 +10,29 @@ import getAllProductPaths from './operations/get-all-product-paths'
import getAllProducts from './operations/get-all-products'
import getProduct from './operations/get-product'
import { API_URL, API_VERSION, CART_COOKIE, CUSTOMER_COOKIE } from '../constants'
import {
API_URL,
API_VERSION,
CART_COOKIE,
CUSTOMER_COOKIE,
TOKEN_COOKIE,
} from '../constants'
export interface OrdercloudConfig extends CommerceAPIConfig {
restFetch: <T>(
restBuyerFetch: <T>(
method: string,
resource: string,
body?: Record<string, unknown>,
fetchOptions?: Record<string, any>
) => Promise<T>,
apiVersion: string;
) => Promise<T>
restMiddlewareFetch: <T>(
method: string,
resource: string,
body?: Record<string, unknown>,
fetchOptions?: Record<string, any>
) => Promise<T>
apiVersion: string
tokenCookie: string
}
const config: OrdercloudConfig = {
@@ -28,8 +41,12 @@ const config: OrdercloudConfig = {
apiVersion: API_VERSION,
cartCookie: CART_COOKIE,
customerCookie: CUSTOMER_COOKIE,
tokenCookie: TOKEN_COOKIE,
cartCookieMaxAge: 2592000,
restFetch: createRestFetcher(() => getCommerceApi().getConfig()),
restBuyerFetch: createBuyerFetcher(() => getCommerceApi().getConfig()),
restMiddlewareFetch: createMiddlewareFetcher(() =>
getCommerceApi().getConfig()
),
fetch: createGraphqlFetcher(() => getCommerceApi().getConfig()),
}

View File

@@ -17,10 +17,10 @@ export default function getAllProductPathsOperation({
config?: Partial<OrdercloudConfig>
} = {}): Promise<T['data']> {
// Get fetch from the config
const { restFetch } = commerce.getConfig(config)
const { restBuyerFetch } = commerce.getConfig(config)
// Get all products
const rawProducts: RawProduct[] = await restFetch<{
const rawProducts: RawProduct[] = await restBuyerFetch<{
Items: RawProduct[]
}>('GET', '/me/products').then((response) => response.Items)

View File

@@ -18,10 +18,10 @@ export default function getAllProductsOperation({
preview?: boolean
} = {}): Promise<T['data']> {
// Get fetch from the config
const { restFetch } = commerce.getConfig(config)
const { restBuyerFetch } = commerce.getConfig(config)
// Get all products
const rawProducts: RawProduct[] = await restFetch<{
const rawProducts: RawProduct[] = await restBuyerFetch<{
Items: RawProduct[]
}>('GET', '/me/products').then((response) => response.Items)

View File

@@ -19,22 +19,22 @@ export default function getProductOperation({
preview?: boolean
} = {}): Promise<T['data']> {
// Get fetch from the config
const { restFetch } = commerce.getConfig(config)
const { restBuyerFetch } = commerce.getConfig(config)
// Get a single product
const productPromise = restFetch<RawProduct>(
const productPromise = restBuyerFetch<RawProduct>(
'GET',
`/me/products/${variables?.slug}`
)
// Get product specs
const specsPromise = restFetch<{ Items: RawSpec[] }>(
const specsPromise = restBuyerFetch<{ Items: RawSpec[] }>(
'GET',
`/me/products/${variables?.slug}/specs`
).then((res) => res.Items)
// Get product variants
const variantsPromise = restFetch<{ Items: RawVariant[] }>(
const variantsPromise = restBuyerFetch<{ Items: RawVariant[] }>(
'GET',
`/me/products/${variables?.slug}/variants`
).then((res) => res.Items)

View File

@@ -23,10 +23,10 @@ export default function getSiteInfoOperation({
preview?: boolean
} = {}): Promise<T['data']> {
// Get fetch from the config
const { restFetch } = commerce.getConfig(config)
const { restBuyerFetch } = commerce.getConfig(config)
// Get list of categories
const rawCategories: RawCategory[] = await restFetch<{
const rawCategories: RawCategory[] = await restBuyerFetch<{
Items: RawCategory[]
}>('GET', `/me/categories`).then((response) => response.Items)

View File

@@ -1,6 +1,6 @@
import Cookies from 'js-cookie'
import vercelFetch from '@vercel/fetch'
import { FetcherError } from '@commerce/utils/errors'
import jwt from 'jsonwebtoken'
import { OrdercloudConfig } from '../index'
@@ -8,7 +8,15 @@ import { OrdercloudConfig } from '../index'
const fetch = vercelFetch()
// Get token util
async function getToken(baseUrl: string) {
async function getToken({
baseUrl,
clientId,
clientSecret,
}: {
baseUrl: string
clientId: string
clientSecret?: string
}): Promise<string> {
// If not, get a new one and store it
const authResponse = await fetch(`${baseUrl}/oauth/token`, {
method: 'POST',
@@ -16,7 +24,7 @@ async function getToken(baseUrl: string) {
'Content-Type': 'application/x-www-form-urlencoded',
Accept: 'application/json',
},
body: `client_id=${process.env.ORDERCLOUD_CLIENT_ID}&client_secret=${process.env.ORDERCLOUD_CLIENT_SECRET}&grant_type=client_credentials`,
body: `client_id=${clientId}&client_secret=${clientSecret}&grant_type=client_credentials`,
})
// If something failed getting the auth response
@@ -32,68 +40,40 @@ async function getToken(baseUrl: string) {
}
// Return the token
return authResponse.json().then((response) => response.access_token)
return authResponse
.json()
.then((response: { access_token: string }) => response.access_token)
}
export async function fetchData<T>(
opts: {
path: string
method: string
baseUrl: string
apiVersion: string
fetchOptions?: Record<string, any>
body?: Record<string, unknown>
},
retries = 0
): Promise<T> {
export async function fetchData<T>(opts: {
token: string
path: string
method: string
config: OrdercloudConfig
fetchOptions?: Record<string, any>
body?: Record<string, unknown>
}): Promise<T> {
// Destructure opts
const { path, body, fetchOptions, baseUrl, apiVersion, method = 'GET' } = opts
// Decode token
const decoded = jwt.decode(global.token as string) as jwt.JwtPayload | null
// If token is not present or its expired, get a new one and store it
if (
!global.token ||
(typeof decoded?.exp === 'number' && decoded?.exp * 1000 < +new Date())
) {
// Get a new one
const token = await getToken(baseUrl)
// Store it
global.token = token
}
const { path, body, fetchOptions, config, token, method = 'GET' } = opts
// Do the request with the correct headers
const dataResponse = await fetch(`${baseUrl}/${apiVersion}${path}`, {
...fetchOptions,
method,
headers: {
...fetchOptions?.headers,
'Content-Type': 'application/json',
accept: 'application/json, text/plain, */*',
authorization: `Bearer ${global.token}`,
},
body: body ? JSON.stringify(body) : undefined,
})
const dataResponse = await fetch(
`${config.commerceUrl}/${config.apiVersion}${path}`,
{
...fetchOptions,
method,
headers: {
...fetchOptions?.headers,
'Content-Type': 'application/json',
accept: 'application/json, text/plain, */*',
authorization: `Bearer ${token}`,
},
body: body ? JSON.stringify(body) : undefined,
}
)
// If something failed getting the data response
if (!dataResponse.ok) {
// If token is expired
if (dataResponse.status === 401) {
// Get a new one
const token = await getToken(baseUrl)
// Store it
global.token = token
}
// And if retries left
if (retries < 2) {
// Refetch
return fetchData(opts, retries + 1)
}
// Get the body of it
const error = await dataResponse.textConverted()
@@ -106,14 +86,20 @@ export async function fetchData<T>(
try {
// Return data response as json
return (await dataResponse.json()) as Promise<T>
const data = (await dataResponse.json()) as Promise<T>
// Return data with meta
return {
meta: { token },
...data,
}
} catch (error) {
// If response is empty return it as text
return null as unknown as Promise<T>
}
}
const serverFetcher: (
export const createMiddlewareFetcher: (
getConfig: () => OrdercloudConfig
) => <T>(
method: string,
@@ -129,17 +115,64 @@ const serverFetcher: (
fetchOptions?: Record<string, any>
) => {
// Get provider config
const { commerceUrl, apiVersion } = getConfig()
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,
baseUrl: commerceUrl,
apiVersion,
config,
path,
body,
})
}
export default serverFetcher
export const createBuyerFetcher: (
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()
// If a token was passed, set it on global
if (fetchOptions?.token) {
global.token = fetchOptions.token
}
// Get a token
if (!global.token) {
global.token = await getToken({
baseUrl: config.commerceUrl,
clientId: process.env.ORDERCLOUD_BUYER_CLIENT_ID as string,
})
}
// Return the data and specify the expected type
return fetchData<T>({
token: global.token as string,
fetchOptions,
config,
method,
path,
body,
})
}

View File

@@ -1,4 +1,5 @@
export const CART_COOKIE = 'ordercloud.cart'
export const TOKEN_COOKIE = 'ordercloud.token'
export const CUSTOMER_COOKIE = 'ordercloud.customer'
export const API_URL = 'https://sandboxapi.ordercloud.io'
export const API_VERSION = 'v1'