by Nick DeJesus
Twitter: @dayhaysoos
IRL Shopping Cart Logic
Internet Shopping Cart Logic
Shopping Carts are mini CRUD Apps
Extra Stuff
stripe
.redirectToCheckout({
lineItems: [
// Replace with the ID of your price
{price: 'price_123', quantity: 1},
],
mode: 'payment',
successUrl: 'https://your-website.com/success',
cancelUrl: 'https://your-website.com/canceled',
})
.then(function(result) {
console.log(result)
});
Important stuff
lineItems: [
{price: 'price_123', quantity: 1},
],
lineItems essentially is the shopping cart just before the user checks out.Â
Problems
But it doesn't have half the information that we need for basic e-commerce experience.
{
"sku_GBJ2Ep8246qeeT": {
"name": "Bananas",
"sku": "sku_GBJ2Ep8246qeeT",
"price": 400,
"image": "image-url here",
"currency": "USD",
"quantity": 1,
"value": 400,
"formattedValue": "$4.00"
}
}
The Object below is an ideal structure for your shopping cart
function Entry(productData, quantity, currency, language) {
return {
...productData,
quantity,
get value() {
return this.price * this.quantity
},
get formattedValue() {
return formatCurrencyString({
value: this.value,
currency,
language
})
}
}
}
This class is the focal point of handling the cart values
function createEntry(product, count) {
const entry = Entry(product, count, action.currency, action.language)
return {
cartDetails: {
...state.cartDetails,
[product.sku]: entry
},
totalPrice: state.totalPrice + product.price * count,
cartCount: state.cartCount + count
}
}
createEntry() is a pure function that returns the desired structure of an item that's been added to the cart
function updateEntry(sku, count) {
const cartDetails = { ...state.cartDetails }
const entry = cartDetails[sku]
if (entry.quantity + count <= 0) return removeEntry(sku)
cartDetails[sku] = Entry(
entry,
entry.quantity + count,
action.currency,
action.language
)
return {
cartDetails,
totalPrice: state.totalPrice + entry.price * count,
cartCount: state.cartCount + count
}
}
updateEntry() is a pure function that returns an updated version of the current cart state
function removeEntry(sku) {
const cartDetails = { ...state.cartDetails }
const totalPrice = state.totalPrice - cartDetails[sku].value
const cartCount = state.cartCount - cartDetails[sku].quantity
delete cartDetails[sku]
return { cartDetails, totalPrice, cartCount }
}
removeEntry() is a function that removes an item from the current cart state
function updateQuantity(sku, quantity) {
const entry = state.cartDetails[sku]
return updateEntry(sku, quantity - entry.quantity)
}
updateQuantity is a pure function that returns cartDetails after updating the quantity of an item
function cartValuesReducer(state, action) {
function createEntry() { ... }
function updateEntry() { ... }
function removeEntry() { ... }
function updateQuantity() { ... }
// switch cases below!
}
switch (action.type) {
case 'add-item-to-cart':
if (action.count <= 0) break
if (action.product.sku in state.cartDetails)
return updateEntry(action.product.sku, action.count)
return createEntry(action.product, action.count)
}
case 'increment-item':
if (action.count <= 0) break
if (action.sku in state.cartDetails)
return updateEntry(action.sku, action.count)
break
case 'decrement-item':
if (action.count <= 0) break
if (action.sku in state.cartDetails)
return updateEntry(action.sku, -action.count)
break
case 'set-item-quantity':
if (action.count < 0) break
if (action.sku in state.cartDetails)
return updateQuantity(action.sku, action.quantity)
break
case 'remove-item-from-cart':
if (action.sku in state.cartDetails) return removeEntry(action.sku)
break
case 'clear-cart':
return cartValuesInitialState
Remember, you're creating an experience. Having data on just the values of the cart isn't enough. You need values that describe the state of the cart
const lineItems = []
for (const sku in cart.cartDetails)
lineItems.push({ price: sku, quantity: cart.cartDetails[sku].quantity })
Everything we've gone through so far was about using Stripe's Checkout on the Client side, but what about Serverless implementations?
again
Instead of the lineItems array, redirectToCheckout also accepts a sessionId
stripe.redirectToCheckout({ sessionId: session.id });
Â
Without a server, it's possible that a malicious user can update the fields on the front end before hitting checkout
Â
Use this to refer to the real values of your product
[
{
"name": "Bananas",
"sku": "sku_GBJ2Ep8246qeeT",
"price": 400,
"image": "banana-url",
"currency": "USD"
},
{
"name": "Tangerines",
"sku": "sku_GBJ2WWfMaGNC2Z",
"price": 100,
"image": "tangerine-url",
"currency": "USD"
}
]
validateCartItems() returns the real product values and formats them to be used as lineItems for Stripe's Checkout
const validateCartItems = (inventorySrc, cartDetails) => {
const validatedItems = []
for (const sku in cartDetails) {
const product = cartDetails[sku]
const inventoryItem = inventorySrc.find(
(currentProduct) => currentProduct.sku === sku
)
if (!inventoryItem) throw new Error(`Product ${sku} not found!`)
const item = {
name: inventoryItem.name,
amount: inventoryItem.price,
currency: inventoryItem.currency,
quantity: product.quantity
}
if (inventoryItem.description) item.description = inventoryItem.description
if (inventoryItem.image) item.images = [inventoryItem.image]
validatedItems.push(item)
}
return validatedItems
}
const inventory = require('./data/products.json')
exports.handler = async (event) => {
try {
const productJSON = JSON.parse(event.body)
const line_items = validateCartItems(inventory, productJSON)
const session = await stripe.checkout.sessions.create({
payment_method_types: ['card'],
billing_address_collection: 'auto',
shipping_address_collection: {
allowed_countries: ['US', 'CA']
},
success_url: `${process.env.URL}/success.html`,
cancel_url: process.env.URL,
line_items
})
return {
statusCode: 200,
body: JSON.stringify({ sessionId: session.id })
}
} catch (error) {
console.error(error)
}
}
line_items is the result of validated cart items
You may want to add some user-centric values to your shopping cart state to better reflect an authenticated experience
You can use event trigger functions for every cart interaction so that you can update your database to reflect these changes. This would be an agnostic approach to supporting backends.
Â
Every business is unique. You might need to do something different from everyone else. Having a solid foundation for your shopping cart gives you the flexibility to be creative with your checkout experiences.
use-shopping-cart handles mostly everything in these slides
nick@echobind.com