-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Cart and order data persistence on the server side (#21)
* Cart and order data persistence on the server side * commerce-generic: - better files naming - comment about the persistence limitation in serverless environment --------- Co-authored-by: Marcin Slezak <[email protected]>
- Loading branch information
1 parent
0404be1
commit df12c21
Showing
17 changed files
with
277 additions
and
85 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import { Cart, CartItem } from '@composable/types' | ||
import products from './products.json' | ||
import { randomUUID } from 'crypto' | ||
|
||
const findProductById = (id: string) => { | ||
return products.find((product) => product.id === id) ?? products[0] | ||
} | ||
|
||
export const generateEmptyCart = (cartId?: string): Cart => ({ | ||
id: cartId || randomUUID(), | ||
items: [], | ||
summary: {}, | ||
}) | ||
|
||
export const generateCartItem = (productId: string, quantity: number) => { | ||
const _product = findProductById(productId) | ||
return { | ||
brand: _product.brand, | ||
category: _product.category, | ||
id: _product.id, | ||
image: _product.images[0], | ||
name: _product.name, | ||
price: _product.price, | ||
quantity: quantity ?? 1, | ||
sku: _product.sku, | ||
slug: _product.slug, | ||
type: _product.type, | ||
} | ||
} | ||
|
||
export const calculateCartSummary = (cartItems: CartItem[]) => { | ||
const subtotal = cartItems.reduce((_subtotal, item) => { | ||
return _subtotal + item.price * (item.quantity ?? 1) | ||
}, 0) | ||
const taxes = subtotal * 0.07 | ||
const total = subtotal + taxes | ||
|
||
return { | ||
subtotalPrice: subtotal.toFixed(2), | ||
taxes: taxes.toFixed(2), | ||
totalPrice: total.toFixed(2), | ||
shipping: 'Free', | ||
} | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
/** | ||
* IMPORTANT: The following implementation is purely for demonstration purposes and should NEVER be used in a Production environment. | ||
* | ||
* The current persistence mechanism leverages storing data as JSON documents in the serverless functions' file system. | ||
* Keep in mind that these files may be destroyed and recreated by the Cloud Provider (e.g., Vercel or Netlify) at any time. | ||
* As a result, users with a working cart containing items may experience data loss and need to start anew if serverless | ||
* functions are reinitialized. This is not suitable for a Production environment where data persistence and reliability are critical. | ||
*/ | ||
import storage from 'node-persist' | ||
import path from 'path' | ||
import os from 'os' | ||
import { Order, Cart } from '@composable/types' | ||
|
||
const storageFolderPath = path.join(os.tmpdir(), 'composable-ui-storage') | ||
|
||
storage.init({ | ||
dir: storageFolderPath, | ||
}) | ||
|
||
export const getOrder = async (orderId: string): Promise<Order | undefined> => { | ||
return storage.getItem(`order-${orderId}`) | ||
} | ||
|
||
export const saveOrder = async (order: Order) => { | ||
await storage.setItem(`order-${order.id}`, order) | ||
return order | ||
} | ||
|
||
export const getCart = async (cartId: string): Promise<Cart | undefined> => { | ||
return storage.getItem(`cart-${cartId}`) | ||
} | ||
|
||
export const saveCart = async (cart: Cart) => { | ||
await storage.setItem(`cart-${cart.id}`, cart) | ||
return cart | ||
} | ||
|
||
export const deleteCart = async (cartId: string) => { | ||
const result = await storage.del(`cart-${cartId}`) | ||
return result.removed | ||
} |
26 changes: 19 additions & 7 deletions
26
packages/commerce-generic/src/services/cart/add-cart-item.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,18 +1,30 @@ | ||
import { CommerceService } from '@composable/types' | ||
import { generateCartData } from '../../data/generateCartData' | ||
import cart from '../../data/cart.json' | ||
import { getCart, saveCart } from '../../data/mock-storage' | ||
import { | ||
generateCartItem, | ||
calculateCartSummary, | ||
generateEmptyCart, | ||
} from '../../data/generate-cart-data' | ||
|
||
export const addCartItem: CommerceService['addCartItem'] = async ({ | ||
cartId, | ||
productId, | ||
quantity, | ||
variantId, | ||
}) => { | ||
const { items, summary } = generateCartData({ productId, quantity }) | ||
const cart = (await getCart(cartId)) || generateEmptyCart(cartId) | ||
|
||
return { | ||
...cart, | ||
items, | ||
summary, | ||
const isProductInCartAlready = cart.items.some( | ||
(item) => item.id === productId | ||
) | ||
|
||
if (isProductInCartAlready) { | ||
cart.items.find((item) => item.id === productId)!.quantity++ | ||
} else { | ||
const newItem = generateCartItem(productId, quantity) | ||
cart.items.push(newItem) | ||
} | ||
cart.summary = calculateCartSummary(cart.items) | ||
|
||
return saveCart(cart) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
import { CommerceService } from '@composable/types' | ||
import cart from '../../data/cart.json' | ||
import { saveCart } from '../../data/mock-storage' | ||
import { generateEmptyCart } from '../../data/generate-cart-data' | ||
|
||
export const createCart: CommerceService['createCart'] = async () => { | ||
return cart | ||
return saveCart(generateEmptyCart()) | ||
} |
17 changes: 15 additions & 2 deletions
17
packages/commerce-generic/src/services/cart/delete-cart-item.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,22 @@ | ||
import { CommerceService } from '@composable/types' | ||
import cart from '../../data/cart.json' | ||
import { getCart, saveCart } from '../../data/mock-storage' | ||
|
||
import { calculateCartSummary } from '../../data/generate-cart-data' | ||
|
||
export const deleteCartItem: CommerceService['deleteCartItem'] = async ({ | ||
cartId, | ||
productId, | ||
}) => { | ||
return cart | ||
const cart = await getCart(cartId) | ||
|
||
if (!cart) { | ||
throw new Error( | ||
`[deleteCartItem] Could not found cart with requested cart id: ${cartId}` | ||
) | ||
} | ||
|
||
cart.items = cart.items.filter((item) => item.id !== productId) | ||
cart.summary = calculateCartSummary(cart.items) | ||
|
||
return saveCart(cart) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,17 +1,10 @@ | ||
import { CommerceService } from '@composable/types' | ||
import { generateCartData } from '../../data/generateCartData' | ||
import cart from '../../data/cart.json' | ||
import { getCart as getCartFromStorage } from '../../data/mock-storage' | ||
|
||
export const getCart: CommerceService['getCart'] = async ({ cartId }) => { | ||
if (!cartId) { | ||
return null | ||
} | ||
|
||
const { items, summary } = generateCartData() | ||
|
||
return { | ||
...cart, | ||
items, | ||
summary, | ||
} | ||
return (await getCartFromStorage(cartId)) || null | ||
} |
29 changes: 22 additions & 7 deletions
29
packages/commerce-generic/src/services/cart/update-cart-item.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,17 +1,32 @@ | ||
import { CommerceService } from '@composable/types' | ||
import { generateCartData } from '../../data/generateCartData' | ||
import cart from '../../data/cart.json' | ||
import { getCart, saveCart } from '../../data/mock-storage' | ||
|
||
import { calculateCartSummary } from '../../data/generate-cart-data' | ||
|
||
export const updateCartItem: CommerceService['updateCartItem'] = async ({ | ||
cartId, | ||
productId, | ||
quantity, | ||
}) => { | ||
const { items, summary } = generateCartData({ productId, quantity }) | ||
const cart = await getCart(cartId) | ||
|
||
if (!cart) { | ||
throw new Error( | ||
`[updateCartItem] Could not found cart with requested cart id: ${cartId}` | ||
) | ||
} | ||
|
||
const cartItem = cart.items.find((item) => item.id === productId) | ||
|
||
return { | ||
...cart, | ||
items, | ||
summary, | ||
if (!cartItem) { | ||
throw new Error( | ||
`[updateCartItem] Could not found cart item with requested product id: ${productId}` | ||
) | ||
} | ||
|
||
cartItem.quantity = quantity | ||
|
||
cart.summary = calculateCartSummary(cart.items) | ||
|
||
return saveCart(cart) | ||
} |
50 changes: 41 additions & 9 deletions
50
packages/commerce-generic/src/services/checkout/create-order.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,16 +1,48 @@ | ||
import { CommerceService } from '@composable/types' | ||
import { generateCartData } from '../../data/generateCartData' | ||
import order from '../../data/order.json' | ||
import { Cart, CheckoutInput, CommerceService, Order } from '@composable/types' | ||
import { getCart } from '../../data/mock-storage' | ||
import { saveOrder } from '../../data/mock-storage' | ||
import shippingMethods from '../../data/shipping-methods.json' | ||
import { randomUUID } from 'crypto' | ||
|
||
export const createOrder: CommerceService['createOrder'] = async ({ | ||
checkout, | ||
}) => { | ||
const cartItems = generateCartData() | ||
const generateOrderFromCart = ( | ||
cart: Cart, | ||
checkoutInput: CheckoutInput | ||
): Order => { | ||
return { | ||
...order, | ||
...cartItems, | ||
id: randomUUID(), | ||
status: 'complete', | ||
payment: 'unpaid', | ||
shipping: 'unfulfilled', | ||
customer: { | ||
email: checkoutInput.customer.email, | ||
}, | ||
shipping_address: { | ||
phone_number: '', | ||
city: '', | ||
...checkoutInput.shipping_address, | ||
}, | ||
billing_address: { | ||
phone_number: '', | ||
city: '', | ||
...checkoutInput.billing_address, | ||
}, | ||
shipping_method: shippingMethods[0], | ||
created_at: Date.now(), | ||
items: cart.items, | ||
summary: cart.summary, | ||
} | ||
} | ||
|
||
export const createOrder: CommerceService['createOrder'] = async ({ | ||
checkout, | ||
}) => { | ||
const cart = await getCart(checkout.cartId) | ||
|
||
if (!cart) { | ||
throw new Error( | ||
`[createOrder] Could not find cart by id: ${checkout.cartId}` | ||
) | ||
} | ||
|
||
return saveOrder(generateOrderFromCart(cart, checkout)) | ||
} |
Oops, something went wrong.
df12c21
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Successfully deployed to the following URLs:
composable-ui-public – ./composable-ui
composable-ui-public-composable.vercel.app
composable-ui-public-git-main-composable.vercel.app
composable-ui-public.vercel.app
storefront.composable.com
df12c21
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.