{"id":28167,"date":"2022-03-21T18:53:05","date_gmt":"2022-03-21T18:53:05","guid":{"rendered":"http:\/\/Building-a-Shopping-Cart-with-Redwood"},"modified":"2022-03-21T18:53:05","modified_gmt":"2022-03-21T18:53:05","slug":"building-a-shopping-cart-with-redwood","status":"publish","type":"post","link":"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-shopping-cart-with-redwood\/","title":{"rendered":"Building a Shopping Cart with Redwood"},"content":{"rendered":"<div class=\"wp-block-cloudinary-markdown \"><p>Having a shopping cart before you check out is something that good online stores have. It shows all of the items you want to be shipped to you and then you get to check out. It\u2019s important to have this functionality implemented securely so that you aren\u2019t putting users\u2019 information at risk<\/p>\n<p>We\u2019re going to build a quick shopping cart with checkout abilities in Redwood, using Stripe to handle all of the checkout functionality.<\/p>\n<h2>Generating the project<\/h2>\n<p>There are a couple of things you\u2019ll want to do before we start writing code. First, <a href=\"https:\/\/dashboard.stripe.com\/register\">create a Stripe account<\/a> if you don\u2019t already have one. <a href=\"https:\/\/cloudinary.com\/users\/register\/free\">Create a Cloudinary account<\/a> as well because this is where we\u2019ll pull the media for the items from. So go ahead and upload the images or videos you want to use for your items. We\u2019ll be using these two services throughout the app. The last thing you\u2019ll need is a local instance of Postgres. You can find the <a href=\"https:\/\/www.postgresql.org\/download\/\">free install here<\/a>.<\/p>\n<p>Now we can go ahead and bootstrap the Redwood app by running the following line in a terminal. This will generate a TypeScript project for us.<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">yarn create redwood-app --typescript shop-checkout\n<\/code><\/span><\/pre>\n<p>You\u2019ll notice a lot of new files and directories in the <code>shop-checkout<\/code> folder. The main folders we\u2019ll be working with are <code>api<\/code>, which holds the back-end code, and <code>web<\/code>, which holds the front-end code.<\/p>\n<p>Since it\u2019s usually a good practice to start building the business logic first, let\u2019s start writing code for the back-end.<\/p>\n<h2>Working on the back-end<\/h2>\n<p>Inside <code>api &gt; db<\/code>, you\u2019ll find a <code>schema.prisma<\/code> file. This is where all of your database changes happen. Whenever you make a change to a model in this file, you\u2019ll need to run a migration for that change to be reflected in your database.<\/p>\n<p>The first thing we need to do is set up the connection to the database. Update the <code>provider<\/code> value from <code>sqlite<\/code> to <code>postgresql<\/code>. That tells Prisma that we\u2019re working with a Postgres database so it knows what to expect. Then we need to set the right value for <code>DATABASE_URL<\/code>.<\/p>\n<p>To do this, look for the <code>.env<\/code> file in the root of the project. You\u2019ll see a line that\u2019s commented out with a <code>DATABASE_URL<\/code> value. Uncomment that line and update it to match your local connection string. That might look something like this where <code>shop<\/code> is the name we\u2019re giving the database.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">DATABASE_URL=postgres:<span class=\"hljs-comment\">\/\/admin:postgres@localhost:5432\/shop<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>That\u2019s all we need to connect our back-end to the database. Now we need to define some models for the tables and rows we need data for.<\/p>\n<h3>Creating the models<\/h3>\n<p>You can delete the <code>UserExample<\/code> model and add the following models.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">model Item {\n  id      <span class=\"hljs-built_in\">String<\/span>  @id @<span class=\"hljs-keyword\">default<\/span>(uuid())\n  name    <span class=\"hljs-built_in\">String<\/span>\n  price   Float\n  url     <span class=\"hljs-built_in\">String<\/span>\n  Order   Order?  @relation(fields: &#91;orderId], <span class=\"hljs-attr\">references<\/span>: &#91;id])\n  orderId <span class=\"hljs-built_in\">String<\/span>?\n}\n\nmodel Order {\n  id         <span class=\"hljs-built_in\">String<\/span>   @id @<span class=\"hljs-keyword\">default<\/span>(cuid())\n  created_at DateTime\n  items      Item&#91;]\n  total      Float\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>We\u2019ve created the models for the <code>Item<\/code> selections customers make in an <code>Order<\/code>. The main thing to note in these models is the relation between the two of these models. The <code>orderId<\/code> acts as a foreign key into the <code>Order<\/code> table. So there can be multiple items associated with one order.<\/p>\n<p>Since we have the models, we can go ahead and run a migration with this command.<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">yarn rw prisma migrate dev\n<\/code><\/span><\/pre>\n<p>You\u2019ll be prompted to name this migration after the connection has been established and you can call it anything you want.<\/p>\n<h3>Seeding the database<\/h3>\n<p>To keep the scope of this tutorial in focus, we\u2019re not going to build out the system for users to select items. Instead, we\u2019re going to seed the database with some items and orders to simulate having an order ready for check out.<\/p>\n<p>In <code>api &gt; db<\/code>, open the <code>seed.js<\/code> file. This is where we\u2019re going to write our seed data. You can delete all of the commented out code in the <code>main<\/code> function and add the code for our models.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">const<\/span> ordersData = &#91;\n    { <span class=\"hljs-attr\">createdAt<\/span>: <span class=\"hljs-keyword\">new<\/span> <span class=\"hljs-built_in\">Date<\/span>(<span class=\"hljs-string\">'09\/21\/2021'<\/span>), <span class=\"hljs-attr\">total<\/span>: <span class=\"hljs-number\">123.34<\/span> },\n    { <span class=\"hljs-attr\">createdAt<\/span>: <span class=\"hljs-keyword\">new<\/span> <span class=\"hljs-built_in\">Date<\/span>(<span class=\"hljs-string\">'04\/21\/2021'<\/span>), <span class=\"hljs-attr\">total<\/span>: <span class=\"hljs-number\">424.13<\/span> },\n  ]\n  <span class=\"hljs-keyword\">const<\/span> itemsData = &#91;\n    { <span class=\"hljs-attr\">name<\/span>: <span class=\"hljs-string\">'Spoon'<\/span>, <span class=\"hljs-attr\">price<\/span>: <span class=\"hljs-number\">34.99<\/span>, <span class=\"hljs-attr\">url<\/span>: <span class=\"hljs-string\">'https:\/\/res.cloudinary.com\/milecia\/image\/upload\/v1606580786\/samples\/landscapes\/landscape-panorama.jpg'<\/span> },\n    { <span class=\"hljs-attr\">name<\/span>: <span class=\"hljs-string\">'Blow Dryer'<\/span>, <span class=\"hljs-attr\">price<\/span>: <span class=\"hljs-number\">89.99<\/span>, <span class=\"hljs-attr\">url<\/span>: <span class=\"hljs-string\">'https:\/\/res.cloudinary.com\/milecia\/image\/upload\/v1606580785\/samples\/landscapes\/nature-mountains.jpg'<\/span> },\n    { <span class=\"hljs-attr\">name<\/span>: <span class=\"hljs-string\">'Pet Bed'<\/span>, <span class=\"hljs-attr\">price<\/span>: <span class=\"hljs-number\">57.99<\/span>, <span class=\"hljs-attr\">url<\/span>: <span class=\"hljs-string\">'https:\/\/res.cloudinary.com\/milecia\/image\/upload\/v1606580779\/samples\/landscapes\/architecture-signs.jpg'<\/span> },\n    { <span class=\"hljs-attr\">name<\/span>: <span class=\"hljs-string\">'Wicker Chair'<\/span>, <span class=\"hljs-attr\">price<\/span>: <span class=\"hljs-number\">124.99<\/span>, <span class=\"hljs-attr\">url<\/span>: <span class=\"hljs-string\">'https:\/\/res.cloudinary.com\/milecia\/image\/upload\/v1606580776\/samples\/landscapes\/girl-urban-view.jpg'<\/span> },\n    { <span class=\"hljs-attr\">name<\/span>: <span class=\"hljs-string\">'Paint'<\/span>, <span class=\"hljs-attr\">price<\/span>: <span class=\"hljs-number\">42.99<\/span>, <span class=\"hljs-attr\">url<\/span>: <span class=\"hljs-string\">'https:\/\/res.cloudinary.com\/milecia\/video\/upload\/v1606580790\/elephant_herd.mp4'<\/span> },\n    { <span class=\"hljs-attr\">name<\/span>: <span class=\"hljs-string\">'Flooring'<\/span>, <span class=\"hljs-attr\">price<\/span>: <span class=\"hljs-number\">15.99<\/span>, <span class=\"hljs-attr\">url<\/span>: <span class=\"hljs-string\">'https:\/\/res.cloudinary.com\/milecia\/video\/upload\/v1606580788\/sea-turtle.mp4'<\/span> },\n  ]\n\n  <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-built_in\">Promise<\/span>.all(\n    ordersData.map(<span class=\"hljs-keyword\">async<\/span> (order) =&gt; {\n      <span class=\"hljs-keyword\">const<\/span> record = <span class=\"hljs-keyword\">await<\/span> db.order.create({\n        <span class=\"hljs-attr\">data<\/span>: { <span class=\"hljs-attr\">createdAt<\/span>: order.createdAt, <span class=\"hljs-attr\">total<\/span>: order.total },\n      })\n      <span class=\"hljs-built_in\">console<\/span>.log(record)\n    }),\n    itemsData.map(<span class=\"hljs-keyword\">async<\/span> (item) =&gt; {\n      <span class=\"hljs-keyword\">const<\/span> record = <span class=\"hljs-keyword\">await<\/span> db.item.create({\n        <span class=\"hljs-attr\">data<\/span>: {<span class=\"hljs-attr\">name<\/span>: item.name, <span class=\"hljs-attr\">price<\/span>: item.price, <span class=\"hljs-attr\">url<\/span>: item.url, <span class=\"hljs-attr\">orderId<\/span>: <span class=\"hljs-string\">'cktm7yt8l00001jxh5f26yg2z'<\/span>},\n      })\n\n      <span class=\"hljs-built_in\">console<\/span>.log(record)\n    })\n  )\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>With this seed data in place, we can go ahead and seed the database with this command.<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">yarn rw prisma db seed\n<\/code><\/span><\/pre>\n<p>This will add all of your data to the database, so when we get ready to load the shopping cart view there\u2019s data available for us.<\/p>\n<h3>Creating the GraphQL types and resolvers<\/h3>\n<p>The last thing we need to do on the back-end is add the methods we need to call the data on the front-end. We\u2019ll wrap this up pretty quick with a couple of Redwood commands to generate the GraphQL types and resolvers we need.<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">yarn rw g sdl item\nyarn rw g sdl order\n<\/code><\/span><\/pre>\n<p>If you take a look in <code>api &gt; src &gt; graphql<\/code>, you\u2019ll see two new files. Take a look at the <code>items.sdl.ts<\/code> file and you\u2019ll see all fo the types you need to make your GraphQL queries and mutations.<\/p>\n<p>Now take a look in <code>api &gt; src &gt; services<\/code>. There are two new folders here that hold a few different files. There are a couple of test files and the main file that holds the resolvers. Open either the <code>items.ts<\/code> file or the <code>orders.ts<\/code> file. You\u2019ll see the resolver to get all of the items or orders.<\/p>\n<p>With that, we\u2019ve finished the back-end! Now we can switch our attention to the front-end, where we\u2019ll build a quick shopping cart and check out page.<\/p>\n<h2>Taking it to the front-end<\/h2>\n<p>Before we start working on components, there is a package we\u2019ll need to add in order to work with Stripe. In a terminal, go to the <code>web<\/code> directory and run this command.<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">yarn add react-stripe-checkout\n<\/code><\/span><\/pre>\n<p>This is the only package we needed to install, so we can get started on the shopping cart page.<\/p>\n<h3>Making the shopping cart<\/h3>\n<p>We\u2019ll use another Redwood command to generate the files we need for the shopping cart. In the terminal, go back to the root of the directory and run this command.<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">yarn rw g page ShoppingCart \/cart\n<\/code><\/span><\/pre>\n<p>This generates a page component that displays at the <code>\/cart<\/code> route. If you take a look in <code>web &gt; src &gt; page<\/code>, you\u2019ll see a new folder named <code>ShoppingCartPage<\/code>. In this folder, there are couple of files related to tests and the main <code>ShoppingCartPage.tsx<\/code> file. Open this up and delete everything inside of the return statement of the component.<\/p>\n<p><em>You can keep the <code>&lt;MetaTags&gt;<\/code> component if you like. I\u2019m just deleting everything to keep things simple.<\/em><\/p>\n<p>First, we\u2019ll add a GraphQL query to get the items. You can look up one of the order ids from the seeded data in your Postgres instance.<\/p>\n<p>Remember, this is supposed to be about the shopping cart and checkout experience so we\u2019ll hard-code an order id. In a fully connected app, the order id would probably come from some parameter being passed from another component.<\/p>\n<p>So let\u2019s add that GraphQL query above the <code>ShoppingCartPage<\/code> component.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> QUERY = gql<span class=\"hljs-string\">`\nquery Items {\n  items {\n    name\n    price\n    url\n    orderId\n  }\n}\n`<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>This will let us get all of the items we have seeded. Next, we need to execute this query so we have the data available and we\u2019re able to filter by order id. At the top of the file, add this import statement.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">import<\/span> { useQuery } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@redwoodjs\/web'<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Inside the <code>ShoppingCartPage<\/code> component, add this code at the top.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">const<\/span> { data, loading } = useQuery(QUERY)\n\n<span class=\"hljs-keyword\">if<\/span> (loading) {\n  <span class=\"hljs-keyword\">return<\/span> <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span>&gt;<\/span>Loading...<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>\n}\n\n<span class=\"hljs-keyword\">const<\/span> orderItems = data.items.filter(<span class=\"hljs-function\"><span class=\"hljs-params\">item<\/span> =&gt;<\/span> item.orderId === <span class=\"hljs-string\">'cktm7yt8l00001jxh5f26yg2z'<\/span>)\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>This will fetch the items from the database and also return a loading state. When the data is loading, we need to handle that condition in the app so it doesn\u2019t crash. That\u2019s why we have that early return statement if the data is still loading.<\/p>\n<p>Once it finishes loading, then we create a new variable to hold all of the items in the order. Now we need to display the info for each item. We\u2019ll add the following return statement to our component.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">return<\/span> (\n  <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">style<\/span>=<span class=\"hljs-string\">{{<\/span>\n    <span class=\"hljs-attr\">display:<\/span> '<span class=\"hljs-attr\">flex<\/span>',\n    <span class=\"hljs-attr\">flexDirection:<\/span> '<span class=\"hljs-attr\">row<\/span>',\n    <span class=\"hljs-attr\">flexWrap:<\/span> '<span class=\"hljs-attr\">wrap<\/span>'\n  }}&gt;<\/span>\n    {orderItems.map(item =&gt; {\n      return (\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">style<\/span>=<span class=\"hljs-string\">{{<\/span>\n          <span class=\"hljs-attr\">border:<\/span> '<span class=\"hljs-attr\">1px<\/span> <span class=\"hljs-attr\">solid<\/span>',\n          <span class=\"hljs-attr\">margin:<\/span> '<span class=\"hljs-attr\">0<\/span> <span class=\"hljs-attr\">12px<\/span> <span class=\"hljs-attr\">24px<\/span>',\n          <span class=\"hljs-attr\">padding:<\/span> '<span class=\"hljs-attr\">24px<\/span>',\n          <span class=\"hljs-attr\">height:<\/span> '<span class=\"hljs-attr\">500px<\/span>',\n          <span class=\"hljs-attr\">width:<\/span> '<span class=\"hljs-attr\">20<\/span>%'\n        }}&gt;<\/span>\n          {item.url.includes('mp4') ?\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">video<\/span> <span class=\"hljs-attr\">controls<\/span> <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">{item.url}<\/span> <span class=\"hljs-attr\">height<\/span>=<span class=\"hljs-string\">{200}<\/span> <span class=\"hljs-attr\">style<\/span>=<span class=\"hljs-string\">{{<\/span> <span class=\"hljs-attr\">maxWidth:<\/span> '<span class=\"hljs-attr\">100<\/span>%' }}&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">video<\/span>&gt;<\/span> :\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">img<\/span> <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">{item.url}<\/span> <span class=\"hljs-attr\">height<\/span>=<span class=\"hljs-string\">{200}<\/span> <span class=\"hljs-attr\">style<\/span>=<span class=\"hljs-string\">{{<\/span> <span class=\"hljs-attr\">maxWidth:<\/span> '<span class=\"hljs-attr\">100<\/span>%' }} \/&gt;<\/span>\n          }\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h2<\/span>&gt;<\/span>{item.name}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h2<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h3<\/span>&gt;<\/span>{item.price}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h3<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n      )\n    })\n    }\n  <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>\n)\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>We have a couple of styled <code>&lt;div&gt;<\/code> elements to make the shopping cart look decent. For each of the items in the order, we check if its media is an image or a video and then we create a card to show the media, the name, and the price.<\/p>\n<p>If you start your app with <code>yarn rw dev<\/code> in a terminal in the root directory and go the \u2018cart\u2019 route, you\u2019ll see something similar to this.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/jesse-thisdot\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/v1632229592\/e-603fc55d218a650069f5228b\/a5zfgdgyzc2arjenabzh.png\" alt=\"picture of the cart with our items in it\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"2000\" height=\"1009\"\/><\/p>\n<p>Now we need to add the Stripe elements to handle the check out after we click the button we\u2019re about to create.<\/p>\n<h3>Adding Stripe for checkouts<\/h3>\n<p>Let\u2019s start by importing that Stripe package. This is a good place to go grab the <a href=\"https:\/\/dashboard.stripe.com\/test\/apikeys\">\u2018Publishable key\u2019 from your Stripe account<\/a> because you\u2019ll need it for the component we\u2019re about to work with.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">import<\/span> StripeCheckout <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'react-stripe-checkout'<\/span>;\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>This adds a simple checkout modal on the page when the customer is ready to check out. All of the checkout and payment functionality is bundled into this one component. We\u2019ll add the component above our list of items.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">StripeCheckout<\/span>\n  <span class=\"hljs-attr\">amount<\/span>=<span class=\"hljs-string\">{36694}<\/span>\n  <span class=\"hljs-attr\">billingAddress<\/span>\n  <span class=\"hljs-attr\">description<\/span>=<span class=\"hljs-string\">{<\/span>`<span class=\"hljs-attr\">The<\/span> ${<span class=\"hljs-attr\">orderItems.length<\/span>} <span class=\"hljs-attr\">items<\/span> <span class=\"hljs-attr\">you<\/span> <span class=\"hljs-attr\">selected<\/span> <span class=\"hljs-attr\">are<\/span> <span class=\"hljs-attr\">here<\/span>`}\n <span class=\"hljs-attr\">image<\/span>=<span class=\"hljs-string\">\"https:\/\/res.cloudinary.com\/milecia\/image\/upload\/v1606580786\/samples\/landscapes\/landscape-panorama.jpg\"<\/span>\n  <span class=\"hljs-attr\">locale<\/span>=<span class=\"hljs-string\">\"auto\"<\/span>\n  <span class=\"hljs-attr\">name<\/span>=<span class=\"hljs-string\">\"Your Cart\"<\/span>\n  <span class=\"hljs-attr\">stripeKey<\/span>=<span class=\"hljs-string\">{process.env.STRIPE_ID}<\/span>\n  <span class=\"hljs-attr\">token<\/span>=<span class=\"hljs-string\">{processToken}<\/span>\n  <span class=\"hljs-attr\">zipCode<\/span>\n\/&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>This component has a ton of props and it would be worthwhile to check out <a href=\"https:\/\/github.com\/azmenak\/react-stripe-checkout\">the docs<\/a> on them. We have the <code>amount<\/code> which could be calculated dynamically, but you\u2019ll have to watch out for the format expected by the component. Then we include the <code>billingAddress<\/code> to show this part of the process.<\/p>\n<p>The main two props to note are the <code>stripeKey<\/code> and the <code>token<\/code>. The <code>stripeKey<\/code> is that publishable key you grabbed from your account earlier. You\u2019ll notice that <code>token<\/code> is a callback that will have the token from the successful Stripe transaction.<\/p>\n<p>We\u2019ll define the method for <code>processToken<\/code> right above the return statement in our component.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">const<\/span> processToken = <span class=\"hljs-function\">(<span class=\"hljs-params\">token<\/span>) =&gt;<\/span> {\n  <span class=\"hljs-built_in\">console<\/span>.log(<span class=\"hljs-string\">'This is where some back-end things would happen after a successful transaction.'<\/span>)\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Usually you\u2019ll pass that token to the back-end and do some processing before saving a record to your database. This is out of scope for this particular tutorial, but if you want to learn more about that back-end implementation, check out <a href=\"https:\/\/stripe.com\/docs\/payments\/checkout\">Stripe\u2019s docs<\/a> on it.<\/p>\n<p>Everything\u2019s in place and ready to go! If you take a look in your browser, you\u2019ll see a new button in the top left of the page.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/jesse-thisdot\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/v1632229684\/e-603fc55d218a650069f5228b\/euppljlffsfaevlel1ky.png\" alt=\"checkout button\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"2000\" height=\"1021\"\/><\/p>\n<p>Go ahead and click the button to see the checkout flow in action.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/jesse-thisdot\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/v1632229701\/e-603fc55d218a650069f5228b\/iigy0sajjeelvagyffnn.png\" alt=\"customer address information\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"2000\" height=\"916\"\/><\/p>\n<p>Since we\u2019re using a test account, you\u2019ll see a yellow indicator in the upper right corner of your page that says you\u2019re in test mode. Now if you continue, this will take you to where a customer can input their card info.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/jesse-thisdot\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/v1632229720\/e-603fc55d218a650069f5228b\/estazechvy5wotinbojj.png\" alt=\"customer card information\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"2000\" height=\"854\"\/><\/p>\n<p>When you submit, you\u2019ll see a loading icon in the pay button and then a green checkmark confirming the transaction.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/jesse-thisdot\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/v1632229745\/e-603fc55d218a650069f5228b\/wit2b4g0iu2eus2qvcfb.png\" alt=\"order confirmation\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"2000\" height=\"1185\"\/><\/p>\n<p>With a successful transaction, the token gets passed to the <code>processToken<\/code> function we made so you\u2019ll see our message in the console.<\/p>\n<p>Now you have a shopping cart with Stripe checkout functionality!<\/p>\n<h2>Finished code<\/h2>\n<p>You can see the full code in <a href=\"https:\/\/github.com\/flippedcoder\/blog-examples\/tree\/main\/shop-checkout\">the <code>shop-checkout<\/code> folder in this repo<\/a> and you can see some of this in action in <a href=\"https:\/\/codesandbox.io\/s\/cocky-matan-ri8nf\">this Code Sandbox<\/a>.<\/p>\n<\/div>\n  \n  <div class=\"wp-block-cloudinary-code-sandbox \">\n    <iframe\n      src=\"https:\/\/codesandbox.io\/embed\/cocky-matan-ri8nf?theme=dark&amp;codemirror=1&amp;highlights=&amp;editorsize=50&amp;fontsize=14&amp;expanddevtools=0&amp;hidedevtools=0&amp;eslint=0&amp;forcerefresh=0&amp;hidenavigation=0&amp;initialpath=%2F&amp;module=&amp;moduleview=0&amp;previewwindow=&amp;view=&amp;runonclick=1\"\n      height=\"500\"\n      style=\"width: 100%;\"\n      title=\"cocky-matan-ri8nf\"\n      loading=\"lazy\"\n      allow=\"accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking\"\n      sandbox=\"allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts\"\n    ><\/iframe>\n  <\/div>\n\n  <div class=\"wp-block-cloudinary-markdown \"><h2>Conclusion<\/h2>\n<p>Knowing how to handle payments is a valuable developer skill because there are so many platforms and applications that rely on payment systems being implemented correctly and securely. It gives you the flexibility to make a site that does everything a customer needs.<\/p>\n<\/div>","protected":false},"excerpt":{"rendered":"","protected":false},"author":41,"featured_media":28168,"comment_status":"","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_cloudinary_featured_overwrite":false,"footnotes":""},"categories":[1],"tags":[134,177,246,371],"class_list":["post-28167","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized","tag-guest-post","tag-javascript","tag-react","tag-under-review"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v25.6 (Yoast SEO v26.9) - https:\/\/yoast.com\/product\/yoast-seo-premium-wordpress\/ -->\n<title>Building a Shopping Cart with Redwood<\/title>\n<meta name=\"description\" content=\"E-commerce sites are popping up every day and maybe you might want to make one. In this tutorial, you&#039;ll learn how to make a basic shopping cart and checkout flow using Stripe. This can be extended to work with more complex functionality as you need.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-shopping-cart-with-redwood\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Building a Shopping Cart with Redwood\" \/>\n<meta property=\"og:description\" content=\"E-commerce sites are popping up every day and maybe you might want to make one. In this tutorial, you&#039;ll learn how to make a basic shopping cart and checkout flow using Stripe. This can be extended to work with more complex functionality as you need.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-shopping-cart-with-redwood\/\" \/>\n<meta property=\"og:site_name\" content=\"Cloudinary Blog\" \/>\n<meta property=\"article:published_time\" content=\"2022-03-21T18:53:05+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925234\/Web_Assets\/blog\/84e1e0b1b1288f29d73ec6b99895b8b96ee0d64b-4032x3024-1_28168f1829\/84e1e0b1b1288f29d73ec6b99895b8b96ee0d64b-4032x3024-1_28168f1829.jpg?_i=AA\" \/>\n\t<meta property=\"og:image:width\" content=\"4032\" \/>\n\t<meta property=\"og:image:height\" content=\"3024\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"NewsArticle\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-shopping-cart-with-redwood\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-shopping-cart-with-redwood\/\"},\"author\":{\"name\":\"\",\"@id\":\"\"},\"headline\":\"Building a Shopping Cart with Redwood\",\"datePublished\":\"2022-03-21T18:53:05+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-shopping-cart-with-redwood\/\"},\"wordCount\":6,\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-shopping-cart-with-redwood\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925234\/Web_Assets\/blog\/84e1e0b1b1288f29d73ec6b99895b8b96ee0d64b-4032x3024-1_28168f1829\/84e1e0b1b1288f29d73ec6b99895b8b96ee0d64b-4032x3024-1_28168f1829.jpg?_i=AA\",\"keywords\":[\"Guest Post\",\"Javascript\",\"React\",\"Under Review\"],\"inLanguage\":\"en-US\",\"copyrightYear\":\"2022\",\"copyrightHolder\":{\"@id\":\"https:\/\/cloudinary.com\/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-shopping-cart-with-redwood\/\",\"url\":\"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-shopping-cart-with-redwood\/\",\"name\":\"Building a Shopping Cart with Redwood\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-shopping-cart-with-redwood\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-shopping-cart-with-redwood\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925234\/Web_Assets\/blog\/84e1e0b1b1288f29d73ec6b99895b8b96ee0d64b-4032x3024-1_28168f1829\/84e1e0b1b1288f29d73ec6b99895b8b96ee0d64b-4032x3024-1_28168f1829.jpg?_i=AA\",\"datePublished\":\"2022-03-21T18:53:05+00:00\",\"description\":\"E-commerce sites are popping up every day and maybe you might want to make one. In this tutorial, you'll learn how to make a basic shopping cart and checkout flow using Stripe. This can be extended to work with more complex functionality as you need.\",\"breadcrumb\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-shopping-cart-with-redwood\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-shopping-cart-with-redwood\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-shopping-cart-with-redwood\/#primaryimage\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925234\/Web_Assets\/blog\/84e1e0b1b1288f29d73ec6b99895b8b96ee0d64b-4032x3024-1_28168f1829\/84e1e0b1b1288f29d73ec6b99895b8b96ee0d64b-4032x3024-1_28168f1829.jpg?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925234\/Web_Assets\/blog\/84e1e0b1b1288f29d73ec6b99895b8b96ee0d64b-4032x3024-1_28168f1829\/84e1e0b1b1288f29d73ec6b99895b8b96ee0d64b-4032x3024-1_28168f1829.jpg?_i=AA\",\"width\":4032,\"height\":3024},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-shopping-cart-with-redwood\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/cloudinary.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Building a Shopping Cart with Redwood\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\",\"url\":\"https:\/\/cloudinary.com\/blog\/\",\"name\":\"Cloudinary Blog\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/cloudinary.com\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\",\"name\":\"Cloudinary Blog\",\"url\":\"https:\/\/cloudinary.com\/blog\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA\",\"width\":312,\"height\":60,\"caption\":\"Cloudinary Blog\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/\"}},{\"@type\":\"Person\",\"@id\":\"\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Building a Shopping Cart with Redwood","description":"E-commerce sites are popping up every day and maybe you might want to make one. In this tutorial, you'll learn how to make a basic shopping cart and checkout flow using Stripe. This can be extended to work with more complex functionality as you need.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-shopping-cart-with-redwood\/","og_locale":"en_US","og_type":"article","og_title":"Building a Shopping Cart with Redwood","og_description":"E-commerce sites are popping up every day and maybe you might want to make one. In this tutorial, you'll learn how to make a basic shopping cart and checkout flow using Stripe. This can be extended to work with more complex functionality as you need.","og_url":"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-shopping-cart-with-redwood\/","og_site_name":"Cloudinary Blog","article_published_time":"2022-03-21T18:53:05+00:00","og_image":[{"width":4032,"height":3024,"url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925234\/Web_Assets\/blog\/84e1e0b1b1288f29d73ec6b99895b8b96ee0d64b-4032x3024-1_28168f1829\/84e1e0b1b1288f29d73ec6b99895b8b96ee0d64b-4032x3024-1_28168f1829.jpg?_i=AA","type":"image\/jpeg"}],"twitter_card":"summary_large_image","schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"NewsArticle","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-shopping-cart-with-redwood\/#article","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-shopping-cart-with-redwood\/"},"author":{"name":"","@id":""},"headline":"Building a Shopping Cart with Redwood","datePublished":"2022-03-21T18:53:05+00:00","mainEntityOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-shopping-cart-with-redwood\/"},"wordCount":6,"publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-shopping-cart-with-redwood\/#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925234\/Web_Assets\/blog\/84e1e0b1b1288f29d73ec6b99895b8b96ee0d64b-4032x3024-1_28168f1829\/84e1e0b1b1288f29d73ec6b99895b8b96ee0d64b-4032x3024-1_28168f1829.jpg?_i=AA","keywords":["Guest Post","Javascript","React","Under Review"],"inLanguage":"en-US","copyrightYear":"2022","copyrightHolder":{"@id":"https:\/\/cloudinary.com\/#organization"}},{"@type":"WebPage","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-shopping-cart-with-redwood\/","url":"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-shopping-cart-with-redwood\/","name":"Building a Shopping Cart with Redwood","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-shopping-cart-with-redwood\/#primaryimage"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-shopping-cart-with-redwood\/#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925234\/Web_Assets\/blog\/84e1e0b1b1288f29d73ec6b99895b8b96ee0d64b-4032x3024-1_28168f1829\/84e1e0b1b1288f29d73ec6b99895b8b96ee0d64b-4032x3024-1_28168f1829.jpg?_i=AA","datePublished":"2022-03-21T18:53:05+00:00","description":"E-commerce sites are popping up every day and maybe you might want to make one. In this tutorial, you'll learn how to make a basic shopping cart and checkout flow using Stripe. This can be extended to work with more complex functionality as you need.","breadcrumb":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-shopping-cart-with-redwood\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/cloudinary.com\/blog\/guest_post\/building-a-shopping-cart-with-redwood\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-shopping-cart-with-redwood\/#primaryimage","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925234\/Web_Assets\/blog\/84e1e0b1b1288f29d73ec6b99895b8b96ee0d64b-4032x3024-1_28168f1829\/84e1e0b1b1288f29d73ec6b99895b8b96ee0d64b-4032x3024-1_28168f1829.jpg?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925234\/Web_Assets\/blog\/84e1e0b1b1288f29d73ec6b99895b8b96ee0d64b-4032x3024-1_28168f1829\/84e1e0b1b1288f29d73ec6b99895b8b96ee0d64b-4032x3024-1_28168f1829.jpg?_i=AA","width":4032,"height":3024},{"@type":"BreadcrumbList","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-shopping-cart-with-redwood\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/cloudinary.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Building a Shopping Cart with Redwood"}]},{"@type":"WebSite","@id":"https:\/\/cloudinary.com\/blog\/#website","url":"https:\/\/cloudinary.com\/blog\/","name":"Cloudinary Blog","description":"","publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/cloudinary.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/cloudinary.com\/blog\/#organization","name":"Cloudinary Blog","url":"https:\/\/cloudinary.com\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA","width":312,"height":60,"caption":"Cloudinary Blog"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":""}]}},"jetpack_featured_media_url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925234\/Web_Assets\/blog\/84e1e0b1b1288f29d73ec6b99895b8b96ee0d64b-4032x3024-1_28168f1829\/84e1e0b1b1288f29d73ec6b99895b8b96ee0d64b-4032x3024-1_28168f1829.jpg?_i=AA","_links":{"self":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/28167","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/users\/41"}],"replies":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/comments?post=28167"}],"version-history":[{"count":0,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/28167\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media\/28168"}],"wp:attachment":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media?parent=28167"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/categories?post=28167"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/tags?post=28167"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}