πŸ‘€ Our most exciting product launch yet πŸš€ Join us May 8th for Sanity Connect

Adding a form to an ecommerce site and sending data to Sanity and Stripe

8 replies
Last updated: Jul 6, 2022
Hey guys, I am admittedly a total noob with the back end. Heck, I am even pretty new to JS in general. I am using React for the first time as well on this project. I have a simple question with hopefully a simple solution. Bonus points if you explain it assuming I am 5 years old. So my issue is this. I currently have an ecommerce shop that sells customizable goods (customizable text, color, etc) all set up through sanity, nextjs, and react... I want to add a simple two or three required text fields form to a product page (probably with react hook form) above two buttons (these buttons pass state to my cart component). I need this form to send the text the customer typed in the field back to the same sanity studio where I push out products to the database (if possible?) so I can view the customer's order requests they input into the form externally (is there a way to have only the successful orders from stripe send back the custom data into the sanity studio?)
Within my ecommerce page is a dynamic single product page where you find the typical things: pictures of product, title of product, description of product all of which are pulled dynamically from the slug. Under the description I have the quantity and then under this I have the add to cart and buy now buttons. The buy now button just adds to cart and opens the cart sidebar component. Within the cart sidebar component there is a proceed to payment button which redirects them to stripes checkout page. This page will redirect them back to either my success or cancel page.
Jul 6, 2022, 12:52 AM
You could setup a webhook in stripe to trigger on successful payments https://stripe.com/docs/webhooks .. Stripe should send your order data in the body of the request, use that to patch in the data into sanity https://www.sanity.io/docs/http-mutations
Jul 6, 2022, 2:04 AM
Thank you so much!!! That actually helps a lot. But how could I collect this data and send it to stripe? I am still a bit confused on how to actually implement the forms for asking what text they would like on their order, etc?
Jul 6, 2022, 2:19 AM
user X
Jul 6, 2022, 2:20 AM
If it helps at all, this is the data structure I mimicked since a lot of this is new to me https://github.com/adrianhajdin/ecommerce_sanity_stripe
Jul 6, 2022, 2:35 AM
user N
Heres a quick way to do in your /product/[slug] page...in sanity create an object for your product options, then patch in the data. You will probably have to return the options object in your stripe.js file too
import React, { useState } from 'react';
import { AiOutlineMinus, AiOutlinePlus, AiFillStar, AiOutlineStar } from 'react-icons/ai';

import { client, urlFor } from '../../lib/client';
import { Product } from '../../components';
import { useStateContext } from '../../context/StateContext';

const ProductDetails = ({ product, products }) => {
  const [options, setOptions] = useState({optionOne: '', optionTwo: ''});
  const { image, name, details, price, } = product;
  product.options = options
  const [index, setIndex] = useState(0);
  const { decQty, incQty, qty, onAdd, setShowCart } = useStateContext();

  console.log(product)
  const handleBuyNow = () => {
    onAdd(product, qty);

    setShowCart(true);
  }

  return (
    <div>
      <div className="product-detail-container">
        <div>
          <div className="image-container">
            <img src={urlFor(image && image[index])} className="product-detail-image" />
          </div>
          <div className="small-images-container">
            {image?.map((item, i) => (
              <img 
                key={i}
                src={urlFor(item)}
                className={i === index ? 'small-image selected-image' : 'small-image'}
                onMouseEnter={() => setIndex(i)}
              />
            ))}
          </div>
        </div>

        <div className="product-detail-desc">
          <h1>{name}</h1>
          <div className="reviews">
            <div>
              <AiFillStar />
              <AiFillStar />
              <AiFillStar />
              <AiFillStar />
              <AiOutlineStar />
            </div>
            <p>
              (20)
            </p>
          </div>
          <h4>Details: </h4>
          <p>{details}</p>
          <p className="price">${price}</p>
          <div className="quantity">
            <h3>Quantity:</h3>
            <p className="quantity-desc">
              <span className="minus" onClick={decQty}><AiOutlineMinus /></span>
              <span className="num">{qty}</span>
              <span className="plus" onClick={incQty}><AiOutlinePlus /></span>
            </p>
          </div>
          <div>
            <label htmlFor='option1'>Option 1</label><input type='text' name='optionOne' onChange={(e) => setOptions({...options, optionOne: e.target.value})}/>
            <label htmlFor='option1'>Option 2</label><input type='text' name='optionTwo' onChange={(e) => setOptions({...options, optionTwo: e.target.value})}/>
          </div>
          <div className="buttons">
            <button type="button" className="add-to-cart" onClick={() => onAdd(product, qty)}>Add to Cart</button>
            <button type="button" className="buy-now" onClick={handleBuyNow}>Buy Now</button>
          </div>
        </div>
      </div>

      <div className="maylike-products-wrapper">
          <h2>You may also like</h2>
          <div className="marquee">
            <div className="maylike-products-container track">
              {products.map((item) => (
                <Product key={item._id} product={item} />
              ))}
            </div>
          </div>
      </div>
    </div>
  )
}

export const getStaticPaths = async () => {
  const query = `*[_type == "product"] {
    slug {
      current
    }
  }
  `;

  const products = await client.fetch(query);

  const paths = products.map((product) => ({
    params: { 
      slug: product.slug.current
    }
  }));

  return {
    paths,
    fallback: 'blocking'
  }
}

export const getStaticProps = async ({ params: { slug }}) => {
  const query = `*[_type == "product" && slug.current == '${slug}'][0]`;
  const productsQuery = '*[_type == "product"]'
  
  const product = await client.fetch(query);
  const products = await client.fetch(productsQuery);

  console.log(product);

  return {
    props: { products, product }
  }
}

export default ProductDetails

stripe.js 
return {
            price_data: { 
              currency: 'usd',
              product_data: { 
                name: item.name,
                images: [newImage],
              },
              unit_amount: item.price * 100,
            },
            adjustable_quantity: {
              enabled:true,
              minimum: 1,
            },
            quantity: item.quantity,
            options: item.options
          }
Jul 6, 2022, 2:54 AM
user N
New to JavaScript and React and you chose to make a headless shop? My friend, you are my hero of the day! πŸ˜›
Jul 6, 2022, 5:55 AM
LOL well, it all started out by me underestimating the complexity behind processing payments securely. Built an entirely static site that was gorgeous but without a tutorial for all of the backend stuff I was helplessly lost on how to execute what I needed with best practice. So I scrapped my progress and started fresh following a youtube tutorial for this. I have officially gone down the rabbit hole, but I have learned a TON and am so excited to get a better understanding of all the fundamentals now that I seen the whole process in action! Struggling through foreign coding languages has been surprisingly addicting haha
user S
Jul 6, 2022, 6:00 AM
I totally get it. I do the seven stages of grief for every major project but I'm still here so something must be keeping me in the mix!
Jul 6, 2022, 2:36 PM

Sanity– build remarkable experiences at scale

Sanity is a modern headless CMS that treats content as data to power your digital business. Free to get started, and pay-as-you-go on all plans.

Was this answer helpful?