Skip to content

Commit

Permalink
Part 25 : Stripe Integration (Backend Part 8)
Browse files Browse the repository at this point in the history
  • Loading branch information
abhishekrathore committed May 9, 2023
1 parent 11fd5ea commit e52c0a1
Show file tree
Hide file tree
Showing 7 changed files with 324 additions and 11 deletions.
20 changes: 20 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
"@headlessui/react": "^1.7.14",
"@heroicons/react": "^2.0.17",
"@reduxjs/toolkit": "^1.9.5",
"@stripe/react-stripe-js": "^2.1.0",
"@stripe/stripe-js": "^1.52.1",
"@tailwindcss/aspect-ratio": "^0.4.2",
"@tailwindcss/forms": "^0.5.3",
"@testing-library/jest-dom": "^5.16.5",
Expand Down
33 changes: 23 additions & 10 deletions src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ import ProductDetailPage from './pages/ProductDetailPage';
import Protected from './features/auth/components/Protected';
import { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { checkAuthAsync, selectLoggedInUser, selectUserChecked } from './features/auth/authSlice';
import {
checkAuthAsync,
selectLoggedInUser,
selectUserChecked,
} from './features/auth/authSlice';
import { fetchItemsByUserIdAsync } from './features/cart/cartSlice';
import PageNotFound from './pages/404';
import OrderSuccessPage from './pages/OrderSuccessPage';
Expand All @@ -27,6 +31,7 @@ import AdminProductFormPage from './pages/AdminProductFormPage';
import AdminOrdersPage from './pages/AdminOrdersPage';
import { positions, Provider } from 'react-alert';
import AlertTemplate from 'react-alert-template-basic';
import StripeCheckout from './pages/StripeCheckout';

const options = {
timeout: 5000,
Expand Down Expand Up @@ -138,6 +143,14 @@ const router = createBrowserRouter([
</Protected>
),
},
{
path: '/stripe-checkout/',
element: (
<Protected>
<StripeCheckout></StripeCheckout>
</Protected>
),
},
{
path: '/logout',
element: <Logout></Logout>,
Expand All @@ -157,26 +170,26 @@ function App() {
const user = useSelector(selectLoggedInUser);
const userChecked = useSelector(selectUserChecked);


useEffect(()=>{
dispatch(checkAuthAsync())
},[dispatch])
useEffect(() => {
dispatch(checkAuthAsync());
}, [dispatch]);

useEffect(() => {

if (user) {
dispatch(fetchItemsByUserIdAsync());
// we can get req.user by token on backend so no need to give in front-end
// we can get req.user by token on backend so no need to give in front-end
dispatch(fetchLoggedInUserAsync());
}
}, [dispatch, user]);

return (
<>
<div className="App">
{ userChecked && <Provider template={AlertTemplate} {...options}>
<RouterProvider router={router} />
</Provider>}
{userChecked && (
<Provider template={AlertTemplate} {...options}>
<RouterProvider router={router} />
</Provider>
)}
{/* Link must be inside the Provider */}
</div>
</>
Expand Down
121 changes: 121 additions & 0 deletions src/Stripe.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@



.Stripe form {
width: 30vw;
min-width: 500px;
align-self: center;
box-shadow: 0px 0px 0px 0.5px rgba(50, 50, 93, 0.1),
0px 2px 5px 0px rgba(50, 50, 93, 0.1), 0px 1px 1.5px 0px rgba(0, 0, 0, 0.07);
border-radius: 7px;
padding: 40px;
}

#payment-message {
color: rgb(105, 115, 134);
font-size: 16px;
line-height: 20px;
padding-top: 12px;
text-align: center;
}

#payment-element {
margin-bottom: 24px;
}

/* Buttons and links */
.Stripe button {
background: #5469d4;
font-family: Arial, sans-serif;
color: #ffffff;
border-radius: 4px;
border: 0;
padding: 12px 16px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
display: block;
transition: all 0.2s ease;
box-shadow: 0px 4px 5.5px 0px rgba(0, 0, 0, 0.07);
width: 100%;
}

.Stripe button:hover {
filter: contrast(115%);
}

.Stripe button:disabled {
opacity: 0.5;
cursor: default;
}

/* spinner/processing state, errors */
.spinner,
.spinner:before,
.spinner:after {
border-radius: 50%;
}

.spinner {
color: #ffffff;
font-size: 22px;
text-indent: -99999px;
margin: 0px auto;
position: relative;
width: 20px;
height: 20px;
box-shadow: inset 0 0 0 2px;
-webkit-transform: translateZ(0);
-ms-transform: translateZ(0);
transform: translateZ(0);
}

.spinner:before,
.spinner:after {
position: absolute;
content: '';
}

.spinner:before {
width: 10.4px;
height: 20.4px;
background: #5469d4;
border-radius: 20.4px 0 0 20.4px;
top: -0.2px;
left: -0.2px;
-webkit-transform-origin: 10.4px 10.2px;
transform-origin: 10.4px 10.2px;
-webkit-animation: loading 2s infinite ease 1.5s;
animation: loading 2s infinite ease 1.5s;
}

.spinner:after {
width: 10.4px;
height: 10.2px;
background: #5469d4;
border-radius: 0 10.2px 10.2px 0;
top: -0.1px;
left: 10.2px;
-webkit-transform-origin: 0px 10.2px;
transform-origin: 0px 10.2px;
-webkit-animation: loading 2s infinite ease;
animation: loading 2s infinite ease;
}

@keyframes loading {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}

@media only screen and (max-width: 600px) {
form {
width: 80vw;
min-width: initial;
}
}
9 changes: 8 additions & 1 deletion src/pages/Checkout.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,19 @@ function Checkout() {
return (
<>
{!items.length && <Navigate to="/" replace={true}></Navigate>}
{currentOrder && (
{currentOrder && currentOrder.paymentMethod ==='cash' && (
<Navigate
to={`/order-success/${currentOrder.id}`}
replace={true}
></Navigate>
)}
{currentOrder && currentOrder.paymentMethod ==='card' && (
<Navigate
to={`/stripe-checkout/`}
replace={true}
></Navigate>
)}

<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div className="grid grid-cols-1 gap-x-8 gap-y-10 lg:grid-cols-5">
<div className="lg:col-span-3">
Expand Down
98 changes: 98 additions & 0 deletions src/pages/CheckoutForm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import React, { useEffect, useState } from "react";
import {
PaymentElement,
useStripe,
useElements
} from "@stripe/react-stripe-js";
import { useSelector } from 'react-redux';
import { selectCurrentOrder } from "../features/order/orderSlice";

export default function CheckoutForm() {
const stripe = useStripe();
const elements = useElements();
const currentOrder = useSelector(selectCurrentOrder);

const [message, setMessage] = useState(null);
const [isLoading, setIsLoading] = useState(false);

useEffect(() => {
if (!stripe) {
return;
}

const clientSecret = new URLSearchParams(window.location.search).get(
"payment_intent_client_secret"
);

if (!clientSecret) {
return;
}

stripe.retrievePaymentIntent(clientSecret).then(({ paymentIntent }) => {
switch (paymentIntent.status) {
case "succeeded":
setMessage("Payment succeeded!");
break;
case "processing":
setMessage("Your payment is processing.");
break;
case "requires_payment_method":
setMessage("Your payment was not successful, please try again.");
break;
default:
setMessage("Something went wrong.");
break;
}
});
}, [stripe]);

const handleSubmit = async (e) => {
e.preventDefault();

if (!stripe || !elements) {
// Stripe.js hasn't yet loaded.
// Make sure to disable form submission until Stripe.js has loaded.
return;
}

setIsLoading(true);

const { error } = await stripe.confirmPayment({
elements,
confirmParams: {
// Make sure to change this to your payment completion page
return_url: `http://localhost:3000/order-success/${currentOrder.id}`,
},
});

// This point will only be reached if there is an immediate error when
// confirming the payment. Otherwise, your customer will be redirected to
// your `return_url`. For some payment methods like iDEAL, your customer will
// be redirected to an intermediate site first to authorize the payment, then
// redirected to the `return_url`.
if (error.type === "card_error" || error.type === "validation_error") {
setMessage(error.message);
} else {
setMessage("An unexpected error occurred.");
}

setIsLoading(false);
};

const paymentElementOptions = {
layout: "tabs"
}

return (
<form id="payment-form" onSubmit={handleSubmit}>
<PaymentElement id="payment-element" options={paymentElementOptions} />
<button disabled={isLoading || !stripe || !elements} id="submit">
<span id="button-text">
{isLoading ? <div className="spinner" id="spinner"></div> : "Pay now"}
</span>
</button>
{/* Show any error or success messages */}
{message && <div id="payment-message">{message}</div>}
</form>
);
}
Loading

0 comments on commit e52c0a1

Please sign in to comment.