A React Hook that handles shopping cart state and logic for Stripe.
# With Yarn
yarn add @stripe/stripe-js use-shopping-cart
# With NPM
npm install --save @stripe/stripe-js use-shopping-cart
At the root level of your application (or the highest point you'll be using Stripe from), wrap your components in a <CartProvider>
.
<CartProvider>
comes with several props that allow you to interact with the Stripe API and customize the Stripe experience.
When loading up Stripe, don't forget to use your public Stripe API key with it. If you need help setting up your environment variables for this, view a list of environment variable tutorials.
Creating a CheckoutSession server-side allows for a more flexible and powerful integration but requires a server component (e.g. a Netlify Function).
At the root level of your app, wrap your Root app in the <CartProvider />
import ReactDOM from 'react-dom'
import { loadStripe } from '@stripe/stripe-js'
import { CartProvider } from 'use-shopping-cart'
import App from './App'
// Remember to add your public Stripe key
const stripePromise = loadStripe(process.env.STRIPE_API_PUBLIC)
ReactDOM.render(
<CartProvider mode="checkout-session" stripe={stripePromise} currency="USD">
<App />
</CartProvider>,
document.getElementById('root')
)
When using CheckoutSessions your product object must adhere to the following shape:
const products = [
{
// Line item name to be shown on the Stripe Checkout page
name: 'Bananas',
// Optional description to be shown on the Stripe Checkout page
description: 'Yummy yellow fruit',
// A unique identifier for this item (stock keeping unit)
sku: 'sku_banana001',
// price in smallest currency unit (e.g. cent for USD)
price: 400,
currency: 'USD',
// Optional image to be shown on the Stripe Checkout page
image: 'https://my-image.com/image.jpg'
}
/* ... more products */
]
Additionally, you must verify the cartItems on the server-side before creating the CheckoutSession. For this you can use the validateCartItems() helper.
To operate a checkout page without any server component you need to enable client-only checkout mode and insert your product information in your Stripe Dashboard:
At the root level of your app, wrap your Root app in the <CartProvider />
import ReactDOM from 'react-dom'
import { loadStripe } from '@stripe/stripe-js'
import { CartProvider } from 'use-shopping-cart'
import App from './App'
// Remember to add your public Stripe key
const stripePromise = loadStripe(process.env.STRIPE_API_PUBLIC)
ReactDOM.render(
<CartProvider
mode="client-only"
stripe={stripePromise}
// The URL to which Stripe should send customers when payment is complete.
successUrl="http://localhost:3000/success"
// The URL to which Stripe should send customers when payment is canceled.
cancelUrl="http://localhost:3000"
currency="USD"
// https://stripe.com/docs/payments/checkout/client#collect-shipping-address
allowedCountries={['US', 'GB', 'CA']}
// https://stripe.com/docs/payments/checkout/client#collect-billing-address
billingAddressCollection={true}
>
<App />
</CartProvider>,
document.getElementById('root')
)
When operating in client-only mode you must set the successUrl
and cancelUrl
props on the CartProvider
component, and the product object must adhere to the following shape:
const products = [
{
name: 'Bananas',
// sku ID from your Stripe Dashboard
sku: 'sku_GBJ2Ep8246qeeT',
// price in smallest currency unit (e.g. cent for USD)
price: 400,
currency: 'USD',
// Optional image to be shown on the Stripe Checkout page
image: 'https://my-image.com/image.jpg'
}
/* ... more products */
]
The hook useShoppingCart()
provides several utilities and pieces of data for you to use in your application. The examples below won't cover every part of the useShoppingCart()
API but you can look at the API below.
import { useShoppingCart } from 'use-shopping-cart'
import { Product } from './Product'
import { CartItems } from './CartItems'
const productData = [
{
name: 'Bananas',
sku: 'sku_GBJ2Ep8246qeeT',
price: 400,
image: 'https://www.fillmurray.com/300/300',
currency: 'USD'
},
{
name: 'Tangerines',
sku: 'sku_GBJ2WWfMaGNC2Z',
price: 100,
image: 'https://www.fillmurray.com/300/300',
currency: 'USD'
}
]
export function App() {
/* Gets the totalPrice and a method for redirecting to stripe */
const { totalPrice, redirectToCheckout, cartCount } = useShoppingCart()
return (
<div>
{/* Renders the products */}
{productData.map((product) => (
<Product key={product.sku} product={product} />
))}
{/* This is where we'll render our cart */}
<p>Number of Items: {cartCount}</p>
<p>Total: {totalPrice()}</p>
<CartItems />
{/* Redirects the user to Stripe */}
<button onClick={() => redirectToCheckout()}>Checkout</button>
</div>
)
}
To add a product to the cart, use useShoppingCart()
's addItem(product)
method. It takes in your product object, which must have a sku
and a price
, and adds it to the cart.
import { useShoppingCart, formatCurrencyString } from 'use-shopping-cart'
export function Product({ product }) {
const { addItem } = useShoppingCart()
/* A helper function that turns the price into a readable format */
const price = formatCurrencyString({
value: product.price,
currency: product.currency,
language: navigator.language
})
return (
<article>
<figure>
<img src={product.image} alt={`Image of ${product.name}`} />
<figcaption>{product.name}</figcaption>
</figure>
<p>{price}</p>
{/* Adds the item to the cart */}
<button
onClick={() => addItem(product)}
aria-label={`Add ${product.name} to your cart`}
>
Add to cart
</button>
</article>
)
}
Once the user has added their items to the cart, you can use the cartDetails
object to display the different data about each product in their cart.
Each product in cartDetails
contains the same data you provided when you called addItem(product)
. In addition, cartDetails
also provides the following properties:
Name | Value |
---|---|
quantity |
Number of that product added to the cart |
value |
The price * quantity |
formattedValue |
A currency formatted version of value |
import { useShoppingCart } from 'use-shopping-cart'
export function CartItems() {
const {
cartDetails,
decrementItem,
incrementItem,
removeItem
} = useShoppingCart()
const cart = []
// Note: Object.keys().map() takes 2x as long as a for-in loop
for (const sku in cartDetails) {
const cartEntry = cartDetails[sku]
// all of your basic product data still exists (i.e. name, image, price)
cart.push(
<article>
{/* image here */}
{/* name here */}
{/* formatted total price of that item */}
<p>Line total: {cartEntry.formattedValue}</p>
{/* What if we want to remove one of the item... or add one */}
<button
onClick={() => decrementItem(cartEntry.sku)}
aria-label={`Remove one ${cartEntry.name} from your cart`}
>
-
</button>
<p>Quantity: {cartEntry.quantity}</p>
<button
onClick={() => incrementItem(cartEntry.sku)}
aria-label={`Add one ${cartEntry.name} to your cart`}
>
+
</button>
{/* What if we don't want this product at all */}
<button
onClick={() => removeItem(cartEntry.sku)}
aria-label={`Remove all ${cartEntry.name} from your cart`}
>
Remove
</button>
</article>
)
}
return cart
}
Note that in the above code, to reduce the quantity of a product in the user's cart, you must pass an SKU to decrementItem()
like so (note that you can also decrease by more than one):
decrementItem(cartEntry.sku)
// or decrease by a count
decrementItem(cartEntry.sku, 3)
This also works the same when trying to increase the quantity of a product:
incrementItem(cartEntry)
// increase by a count
incrementItem(cartEntry.sku, 4)
Just like you can reduce or increase the quantity of a product you can remove the product entirely with removeItem()
:
removeItem(cartEntry.sku)
This is achievable with the setItemQuantity(sku, quantity)
method (introduced in 2.0.0). It takes an SKU of a product in the cart and the quantity of that product you wish to have. Here is a very simple example:
export function GiveMeFiveDonutsPlease() {
const { setItemQuantity } = useShoppingCart()
return (
<button onClick={() => setItemQuantity('donuts-1a2b3c', 5)}>
Set donut quantity to 5
</button>
)
}
For a real-world robust example visit the documentation for setItemQuantity
.
You can view the full API on our documentation page.
Props for this component in Client-only mode:
Name | Type |
---|---|
mode |
"client-only" |
stripe |
Stripe | undefined |
successUrl |
string |
cancelUrl |
string |
currency |
string |
language |
string |
billingAddressCollection |
boolean |
allowedCountries |
null | string[] |
And now, CheckoutSession mode:
Name | Type |
---|---|
mode |
"checkout-session" |
stripe |
Stripe | undefined |
currency |
string |
language |
string |
Returns an object with all the following properties and methods:
Name | Type/Args | Return Type |
---|---|---|
addItem() |
product: Object | N/A |
incrementItem() |
sku: string | N/A |
decrementItem() |
sku: string | N/A |
removeItem() |
sku: string | N/A |
setItemQuantity() |
sku: string, quantity: number | N/A |
totalPrice |
N/A | number |
formattedTotalPrice |
N/A | string |
cartCount |
number | N/A |
cartDetails |
Object of cart entries | N/A |
redirectToCheckout() |
sessionId?: string | Error (if one occurrs) |
clearCart() |
N/A | N/A |
This function takes one options argument, these are the options for this function:
Name | Type |
---|---|
value | number |
currency | string |
language | string (optional) |
The following tutorials teach how to set up your custom environment variables for your project.
MIT © dayhaysoos
If you're working on this project don't forget to check out the CONTRIBUTING.md file.
Before you run any of the examples be sure to set your environment variables at the root of
the project in a .env.development
file (or documentation/.env.development
for the documentation workspace). There is a .env.example
and a documentation/.env.example
file with the example variables you'll need to run the examples and documentation workspaces in this project. You'll need to fill them in with your own API keys from Stripe.
# .env.example
STRIPE_API_PUBLIC=
STRIPE_API_SECRET=
# documentation/.env.example
GATSBY_STRIPE_PUBLISHABLE_KEY=
Here are a couple commands to get you started in development:
# Run the development environment which builds use-shopping-cart in watch-mode
# and starts the CRA example with Netlify functions
yarn dev
# Runs tests in watch-mode
yarn test
# Runs the documentation page locally
yarn dev:docs
Please make all README edits to /use-shopping-cart/README.md
. All edits to /README.md
will be overwritten on
commit by /use-shopping-cart/README.md
. Consider /README.md
read-only.
We created this hook with create-react-hook.
Thanks goes to these wonderful people (emoji key):
This project follows the all-contributors specification. Contributions of any kind welcome!