From 47f9e9d371b7a433e8dc684cc0abe7f54db4b13b Mon Sep 17 00:00:00 2001 From: Juthatip McDevitt Date: Fri, 24 May 2024 16:21:34 -0500 Subject: [PATCH] created checkout route and connected with Stripe to checkout order --- donutshop_ecommerce/package-lock.json | 45 +++++++---- donutshop_ecommerce/package.json | 1 + .../src/app/api/checkout/route.js | 77 +++++++++++++++++++ .../src/app/api/models/Order.js | 35 +++++++++ donutshop_ecommerce/src/app/cart/page.js | 30 +++++--- 5 files changed, 161 insertions(+), 27 deletions(-) create mode 100644 donutshop_ecommerce/src/app/api/checkout/route.js create mode 100644 donutshop_ecommerce/src/app/api/models/Order.js diff --git a/donutshop_ecommerce/package-lock.json b/donutshop_ecommerce/package-lock.json index d2eaa4b..b2eadea 100644 --- a/donutshop_ecommerce/package-lock.json +++ b/donutshop_ecommerce/package-lock.json @@ -23,6 +23,7 @@ "react-scroll-parallax": "^3.4.5", "react-slick": "^0.30.2", "slick-carousel": "^1.8.1", + "stripe": "^15.8.0", "uniqid": "^5.4.0" }, "devDependencies": { @@ -2722,7 +2723,6 @@ "version": "20.12.12", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.12.tgz", "integrity": "sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==", - "dev": true, "dependencies": { "undici-types": "~5.26.4" } @@ -3313,7 +3313,6 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", - "dev": true, "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", @@ -3602,7 +3601,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dev": true, "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", @@ -3784,7 +3782,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", - "dev": true, "dependencies": { "get-intrinsic": "^1.2.4" }, @@ -3796,7 +3793,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, "engines": { "node": ">= 0.4" } @@ -4532,7 +4528,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -4610,7 +4605,6 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", - "dev": true, "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2", @@ -4775,7 +4769,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dev": true, "dependencies": { "get-intrinsic": "^1.1.3" }, @@ -4816,7 +4809,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dev": true, "dependencies": { "es-define-property": "^1.0.0" }, @@ -4828,7 +4820,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -4840,7 +4831,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -4872,7 +4862,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, "dependencies": { "function-bind": "^1.1.2" }, @@ -6074,7 +6063,6 @@ "version": "1.13.1", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", - "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -6608,6 +6596,20 @@ "node": ">=6" } }, + "node_modules/qs": { + "version": "6.12.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.12.1.tgz", + "integrity": "sha512-zWmv4RSuB9r2mYQw3zxQuHWeU+42aKi1wWig/j4ele4ygELZ7PEO6MM7rim9oAQH2A5MWfsAVf/jPvTPgCbvUQ==", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -6991,7 +6993,6 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "dev": true, "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", @@ -7044,7 +7045,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", - "dev": true, "dependencies": { "call-bind": "^1.0.7", "es-errors": "^1.3.0", @@ -7314,6 +7314,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/stripe": { + "version": "15.8.0", + "resolved": "https://registry.npmjs.org/stripe/-/stripe-15.8.0.tgz", + "integrity": "sha512-7eEPMgehd1I16cXeP7Rcn/JKkPWIadB9vGIeE+vbCzQXaY5R95AoNmkZx0vmlu1H4QIDs7j1pYIKPRm9Dr4LKg==", + "dependencies": { + "@types/node": ">=8.1.0", + "qs": "^6.11.0" + }, + "engines": { + "node": ">=12.*" + } + }, "node_modules/strnum": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", @@ -7671,8 +7683,7 @@ "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" }, "node_modules/uniqid": { "version": "5.4.0", diff --git a/donutshop_ecommerce/package.json b/donutshop_ecommerce/package.json index 7c6969c..c7003c8 100644 --- a/donutshop_ecommerce/package.json +++ b/donutshop_ecommerce/package.json @@ -24,6 +24,7 @@ "react-scroll-parallax": "^3.4.5", "react-slick": "^0.30.2", "slick-carousel": "^1.8.1", + "stripe": "^15.8.0", "uniqid": "^5.4.0" }, "devDependencies": { diff --git a/donutshop_ecommerce/src/app/api/checkout/route.js b/donutshop_ecommerce/src/app/api/checkout/route.js new file mode 100644 index 0000000..61ef532 --- /dev/null +++ b/donutshop_ecommerce/src/app/api/checkout/route.js @@ -0,0 +1,77 @@ +import mongoose from "mongoose"; +import { getServerSession } from "next-auth" +import { authOptions } from "../auth/[...nextauth]/route" +import { Order } from "../models/Order"; +import { MenuItem } from "../models/MenuItem" +const stripe = require('stripe')(process.env.STRIPE_SK); + + +export async function POST(req){ + mongoose.connect(process.env.MONGO_URL); + + const {address ,cartProducts} = await req.json(); + const session = await getServerSession(authOptions); + const userEmail = session?.user?.email; + + const orderDoc = await Order.create({ + userEmail, + ...address, + cartProducts, + paid: false, + }) + + const stripeLineItems = []; + for(const cartProduct of cartProducts){ + + const productInfo = await MenuItem.findById(cartProduct._id); + let productPrice = productInfo.basePrice; + if(cartProduct.size){ + const size = productInfo.sizes.find(size => size._id.toString() === cartProduct.size._id.toString()); + productPrice += size.price; + } + if(cartProduct.extra?.length > 0){ + for(const cartProductExtra of cartProduct.extra){ + const productExtra = productInfo.extraItems; + const extraToppingInfo = productExtra.find(extras => extras._id.toString() === cartProductExtra._id.toString()) + productPrice += extraToppingInfo.price + } + } + + const productName = cartProduct.itemName + + stripeLineItems.push({ + quantity: 1, + price_data: { + currency: 'USD', + product_data: { + name: productName, + }, + unit_amount: productPrice * 100, + }, + }); + } + + + const stripeSession = await stripe.checkout.sessions.create({ + line_items: stripeLineItems, + mode: 'payment', + customer_email: userEmail, + success_url: process.env.NEXTAUTH_URL + 'orders/' + orderDoc._id.toString() + '?clear-cart=1', + cancel_url: process.env.NEXTAUTH_URL + 'cart?canceled=1', + metadata: {orderId: orderDoc._id.toString()}, + payment_intent_data: { + metadata:{orderId:orderDoc._id.toString()}, + }, + shipping_options: [ + { + shipping_rate_data: { + display_name: 'Delivery fee', + type: 'fixed_amount', + fixed_amount: {amount: 500, currency: 'USD'}, + }, + } + ], + }); + + return Response.json(stripeSession.url); +} \ No newline at end of file diff --git a/donutshop_ecommerce/src/app/api/models/Order.js b/donutshop_ecommerce/src/app/api/models/Order.js new file mode 100644 index 0000000..68fefe6 --- /dev/null +++ b/donutshop_ecommerce/src/app/api/models/Order.js @@ -0,0 +1,35 @@ +import { Schema, model, models } from "mongoose"; + +const OrderSchema = new Schema({ + userEmail: { + type: String + }, + phoneNumber:{ + type: String, + }, + streetAddress:{ + type: String, + }, + city:{ + type: String, + }, + stateProvince:{ + type: String, + }, + zipCode:{ + type: String, + }, + country:{ + type: String, + }, + cartProducts: { + type: Object, + }, + paid: { + type: Boolean, + default: false, + }, + +}, {timestamps: true}) + +export const Order = models?.Order || model('Order', OrderSchema); \ No newline at end of file diff --git a/donutshop_ecommerce/src/app/cart/page.js b/donutshop_ecommerce/src/app/cart/page.js index bd138a6..1773c0e 100644 --- a/donutshop_ecommerce/src/app/cart/page.js +++ b/donutshop_ecommerce/src/app/cart/page.js @@ -18,23 +18,33 @@ const CartPage = () => { setAddress(addressFormProfile) } }, [profileCheckoutData]) + + //cart total + let subtotal = 0 + for(const p of cartProducts){ + subtotal += productTotal(p) + } + + function handleAddresschange(propName, value){ setAddress(prevAddress => ({...prevAddress, [propName]:value})); } //checkout to Stripe - function handleCheckout(ev){ - fetch('/api/checkout', { - - }) + async function handleCheckout(ev){ + ev.preventDefault(); + //info and cart products + const response = await fetch('/api/checkout', { + method: 'POST', + headers: {'Content-Type':'application/json'}, + body: JSON.stringify({address, cartProducts,}), + }); + window.location = await response.json() } + console.log({cartProducts}) - //cart total - let subtotal = 0 - for(const p of cartProducts){ - subtotal += productTotal(p) - } + return ( @@ -96,7 +106,7 @@ const CartPage = () => {

Shipping Address

- +