created checkout route and connected with Stripe to checkout order

This commit is contained in:
Juthatip McDevitt 2024-05-24 16:21:34 -05:00
parent 0b6516e19d
commit 47f9e9d371
5 changed files with 161 additions and 27 deletions

View file

@ -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",

View file

@ -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": {

View file

@ -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);
}

View file

@ -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);

View file

@ -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 = () => {
<p className='uppercase font-semibold mb-5 text-center'>Shipping Address</p>
<form onSubmit={handleCheckout} className='border-t py-5'>
<AddressInfo addressProps={address} setAddressProps={handleAddresschange}/>
<button type='button' className='w-full rounded-md bg-pink-500 text-white hover:opacity-80 duration-300 p-2 block'>Process to pay</button>
<button type='submit' className='w-full rounded-md bg-pink-500 text-white hover:opacity-80 duration-300 p-2 block'>Process to pay</button>
</form>
</div>
</div>