Skip to content

Commit

Permalink
Feat: Stripe Payments and Orders dashboard 💵
Browse files Browse the repository at this point in the history
  • Loading branch information
Ali-Raza764 committed Jun 8, 2024
1 parent e4ae75f commit e7e1a73
Show file tree
Hide file tree
Showing 17 changed files with 242 additions and 348 deletions.
27 changes: 27 additions & 0 deletions actions/admin/order.actions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"use server";

import Order from "@/lib/models/Order";
import dbConnect from "@/utils/dbConnect";

export const createOrder = async (payload) => {
await dbConnect();
try {
const order = await Order.create(payload);
if (order) {
console.log(order);
return {
status: 200,
message: "Order Created Successfully",
data: order,
};
} else {
return {
status: 401,
message: "Some Error Occured",
data: order,
};
}
} catch (error) {
throw new Error(error);
}
};
49 changes: 49 additions & 0 deletions actions/stripe/sessions.actions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
"use server";
import { auth } from "@/auth";
import Stripe from "stripe";

const stripe = new Stripe(process.env.NEXT_STRIPE_SECRET_KEY);

export const createCheckout = async (data) => {
const authSession = await auth();
if (!authSession) {
return {
status: 400,
message: "Unauthorized",
};
}
const { productId, name, unitPrice, quantity, images } = data;
console.log(data);
const session = await stripe.checkout.sessions.create({
payment_method_types: ["card"],
shipping_address_collection: {
allowed_countries: ['US', 'CA'], // Add other allowed countries as needed
},
metadata: {
userId: authSession.user.id,
productId,
},
line_items: [
{
price_data: {
currency: "usd",

product_data: {
name: name,
images,
},
unit_amount: unitPrice*100, // Price in cents
},
quantity: quantity,
},
],

mode: "payment",
success_url: `http:https://localhost:3000/success`,
cancel_url: `http:https://localhost:3000/cancel`,
});
return {
status: 200,
checkoutUrl: session.url,
};
};
7 changes: 7 additions & 0 deletions app/(root)/cancel/page.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import React from "react";

const CancelPage = () => {
return <div>You Cancelled Product Purchase</div>;
};

export default CancelPage;
30 changes: 24 additions & 6 deletions app/(root)/productdetails/[id]/ProductDetails.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import {
} from "react-icons/ai";
import { BsStarFill, BsStarHalf } from "react-icons/bs";
import { FaHeart } from "react-icons/fa";
import AddToCartButton from "../../../../components/reuseable/AddToCartButton";
import AddToCartButton from "@/components/reuseable/AddToCartButton";
import BuyItemButton from "@/components/reuseable/BuyItemButton";

const ProductDetails = ({
id,
Expand All @@ -15,6 +16,7 @@ const ProductDetails = ({
price,
category,
excerpt,
images,
}) => {
return (
<div className="lg:w-1/2 w-full md::pl-10 md::py-6 mt-6 md:mt-0">
Expand Down Expand Up @@ -77,17 +79,33 @@ const ProductDetails = ({
{price}$
</span>
<div className="flex items-center gap-3">
<button className="flex ml-auto text-white bg-red-500 border-0 py-2 px-6 focus:outline-none hover:bg-red-600 rounded transition">
Buy Now
</button>
<AddToCartButton id={id} text={true} className={'bg-gray-900 hover:bg-gray-700 transition px-4 p-2 rounded-md text-white'} icon={false}/>
<BuyItemButton
className={
"flex ml-auto text-white bg-red-500 border-0 py-2 px-6 focus:outline-none hover:bg-red-600 rounded transition"
}
data={{
productId: id,
name,
unitPrice: price,
quantity: 1,
images,
}}
/>
<AddToCartButton
id={id}
text={true}
className={
"bg-gray-900 hover:bg-gray-700 transition px-4 p-2 rounded-md text-white"
}
icon={false}
/>
<button className="rounded-full w-10 h-10 bg-gray-200 p-0 border-0 inline-flex items-center justify-center text-gray-500 ml-4 hover:scale-110 transition">
<FaHeart />
</button>
</div>
</div>
</div>
);icon
);
};

export default ProductDetails;
4 changes: 2 additions & 2 deletions app/(root)/productdetails/[id]/page.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import Product from "@/lib/models/Product";
import dbConnect from "@/utils/dbConnect";

const getProduct = async (id) => {
dbConnect();
await dbConnect();
const res = await Product.findById(id);
if (res) {
return res;
Expand All @@ -16,7 +16,6 @@ const getProduct = async (id) => {

const ProductDetailsPage = async ({ params }) => {
const product = await getProduct(params.id);
console.log(product.images);
return (
<main className="w-full min-h-screen px-3 md:px-11 py-8">
<section className="text-gray-600 w-full flex lg:flex-row flex-col lg:gap-3">
Expand All @@ -28,6 +27,7 @@ const ProductDetailsPage = async ({ params }) => {
price={product.price}
category={product.category}
excerpt={product.excerpt}
images={product.images}
/>
</section>
<section aria-label="related products" className="w-full my-6 ">
Expand Down
7 changes: 7 additions & 0 deletions app/(root)/success/page.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import React from "react";

const SuccessPage = () => {
return <div>Product Buyed Successfully</div>;
};

export default SuccessPage;
125 changes: 39 additions & 86 deletions app/admin/_components/Table.jsx
Original file line number Diff line number Diff line change
@@ -1,89 +1,42 @@
"use client";
import {
Table,
TableBody,
TableCaption,
TableCell,
TableFooter,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table"

const invoices = [
{
invoice: "INV001",
paymentStatus: "Paid",
totalAmount: "$250.00",
paymentMethod: "Credit Card",
},
{
invoice: "INV002",
paymentStatus: "Pending",
totalAmount: "$150.00",
paymentMethod: "PayPal",
},
{
invoice: "INV003",
paymentStatus: "Unpaid",
totalAmount: "$350.00",
paymentMethod: "Bank Transfer",
},
{
invoice: "INV004",
paymentStatus: "Paid",
totalAmount: "$450.00",
paymentMethod: "Credit Card",
},
{
invoice: "INV005",
paymentStatus: "Paid",
totalAmount: "$550.00",
paymentMethod: "PayPal",
},
{
invoice: "INV006",
paymentStatus: "Pending",
totalAmount: "$200.00",
paymentMethod: "Bank Transfer",
},
{
invoice: "INV007",
paymentStatus: "Unpaid",
totalAmount: "$300.00",
paymentMethod: "Credit Card",
},
]

export function TableDemo() {
return (
<Table>
{/* <TableCaption>A list of your recent invoices.</TableCaption> */}
<TableHeader>
<TableRow>
<TableHead className="w-[100px]">Invoice</TableHead>
<TableHead>Status</TableHead>
<TableHead>Method</TableHead>
<TableHead className="text-right">Amount</TableHead>
Table,
TableBody,
TableCell,
TableFooter,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";

export function TableDemo({ data }) {
return (
<Table>
{/* <TableCaption>A list of your recent invoices.</TableCaption> */}
<TableHeader>
<TableRow>
<TableHead className="w-[100px]">Order</TableHead>
<TableHead>Status</TableHead>
<TableHead>Product</TableHead>
<TableHead className="text-right">Amount</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{data.map((order) => (
<TableRow key={order._id}>
<TableCell className="font-medium">{order._id}</TableCell>
<TableCell> {order.pricePaid ? "YES" : "NO"}</TableCell>
<TableCell>{order.productId}</TableCell>
<TableCell className="text-right">{order.pricePaid}</TableCell>
</TableRow>
</TableHeader>
<TableBody>
{invoices.map((invoice) => (
<TableRow key={invoice.invoice}>
<TableCell className="font-medium">{invoice.invoice}</TableCell>
<TableCell>{invoice.paymentStatus}</TableCell>
<TableCell>{invoice.paymentMethod}</TableCell>
<TableCell className="text-right">{invoice.totalAmount}</TableCell>
</TableRow>
))}
</TableBody>
<TableFooter>
<TableRow>
<TableCell colSpan={3}>Total</TableCell>
<TableCell className="text-right">$2,500.00</TableCell>
</TableRow>
</TableFooter>
</Table>
)
}

))}
</TableBody>
<TableFooter>
<TableRow>
<TableCell colSpan={3}>Total</TableCell>
<TableCell className="text-right">$2,500.00 etc..</TableCell>
</TableRow>
</TableFooter>
</Table>
);
}
7 changes: 4 additions & 3 deletions app/admin/orders/page.jsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import React from "react";
import { TableDemo } from "../_components/Table";
import { fetchOrders } from "@/utils/fetchAllItems";

const Orders = () => {
const Orders = async () => {
const orders = await fetchOrders();
return (
<div className="w-full min-h-screen">
<h1 className="text-3xl font-bold font-sans">Orders 📮</h1>
<div className="p-11 h-[75vh] overflow-y-auto">
<TableDemo />
<TableDemo data={orders} />
</div>
</div>
);
Expand Down
20 changes: 17 additions & 3 deletions app/api/webhooks/stripe/route.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import Stripe from "stripe";
import { NextResponse } from "next/server";
import { createOrder } from "@/actions/admin/order.actions";

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
const stripe = new Stripe(process.env.NEXT_STRIPE_SECRET_KEY);
export async function POST(req, res) {
const payload = await req.text();
const response = JSON.parse(payload);
Expand All @@ -18,8 +19,21 @@ export async function POST(req, res) {
process.env.STRIPE_WEBHOOK_SECRET
);

console.log("event:", event.type, event.data); //checkout.session.completed holds the metadata

if (event.type == "checkout.session.completed") {
const userId = event.data.object.metadata.userId;
const productId = event.data.object.metadata.productId;
const shippingAddress = event.data.object.customer_details?.address;
const pricePaid = event.data.object.amount_total;
// console.log(userId, productId, shippingAddress, pricePaid);
const res = await createOrder({
userId,
productId,
shippingAddress,
pricePaid,
quantity: 1,
});
// console.log(res);
}
return NextResponse.json({
status: "success",
event: event.type,
Expand Down
20 changes: 20 additions & 0 deletions components/reuseable/BuyItemButton.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
"use client";

import { createCheckout } from "@/actions/stripe/sessions.actions";

const BuyItemButton = ({ className, data }) => {
const handleClick = async () => {
const checkOutUrl = await createCheckout(data);
window.open(checkOutUrl.checkoutUrl);
};
return (
<button
className={className + " px-4 p-3 text-md font-semibold "}
onClick={handleClick}
>
Buy Now
</button>
);
};

export default BuyItemButton;
Loading

0 comments on commit e7e1a73

Please sign in to comment.