diff --git a/backend/apps/order/views.py b/backend/apps/order/views.py index 63d2e71..6ca7e43 100644 --- a/backend/apps/order/views.py +++ b/backend/apps/order/views.py @@ -34,7 +34,6 @@ def post(self, request, format=None): } ], ) - print("checkout_session ", checkout_session) return Response({"sessionId": checkout_session["id"]}) except Exception as e: print("e ", e) diff --git a/frontend/contract-ui/contracts/PetLocal.json b/frontend/contract-ui/contracts/PetLocal.json index a9c11d5..ecb6341 100644 --- a/frontend/contract-ui/contracts/PetLocal.json +++ b/frontend/contract-ui/contracts/PetLocal.json @@ -1 +1 @@ -{"abi":"[{\"type\":\"constructor\",\"payable\":false,\"inputs\":[{\"type\":\"uint256\",\"name\":\"initialPetIndex\"}]},{\"type\":\"function\",\"name\":\"addPet\",\"constant\":false,\"payable\":false,\"inputs\":[],\"outputs\":[]},{\"type\":\"function\",\"name\":\"addToCart\",\"constant\":false,\"payable\":false,\"inputs\":[{\"type\":\"uint256\",\"name\":\"_petId\"},{\"type\":\"string\",\"name\":\"_petName\"},{\"type\":\"string\",\"name\":\"_petColor\"},{\"type\":\"uint256\",\"name\":\"_petPrice\"},{\"type\":\"string\",\"name\":\"_petPhoto\"}],\"outputs\":[]},{\"type\":\"function\",\"name\":\"adoptPet\",\"constant\":false,\"payable\":false,\"inputs\":[{\"type\":\"uint256\",\"name\":\"adoptIdx\"}],\"outputs\":[]},{\"type\":\"function\",\"name\":\"allAdoptedPets\",\"constant\":true,\"stateMutability\":\"view\",\"payable\":false,\"inputs\":[{\"type\":\"uint256\"}],\"outputs\":[{\"type\":\"uint256\"}]},{\"type\":\"function\",\"name\":\"getAllAdoptedPets\",\"constant\":true,\"stateMutability\":\"view\",\"payable\":false,\"inputs\":[],\"outputs\":[{\"type\":\"uint256[]\"}]},{\"type\":\"function\",\"name\":\"getAllAdoptedPetsByOwner\",\"constant\":true,\"stateMutability\":\"view\",\"payable\":false,\"inputs\":[],\"outputs\":[{\"type\":\"uint256[]\"}]},{\"type\":\"function\",\"name\":\"getCartItems\",\"constant\":true,\"stateMutability\":\"view\",\"payable\":false,\"inputs\":[],\"outputs\":[{\"type\":\"tuple[]\",\"components\":[{\"type\":\"uint256\",\"name\":\"id\"},{\"type\":\"string\",\"name\":\"name\"},{\"type\":\"string\",\"name\":\"color\"},{\"type\":\"uint256\",\"name\":\"price\"},{\"type\":\"string\",\"name\":\"photo\"}]}]},{\"type\":\"function\",\"name\":\"getCartLength\",\"constant\":true,\"stateMutability\":\"view\",\"payable\":false,\"inputs\":[],\"outputs\":[{\"type\":\"uint256\"}]},{\"type\":\"function\",\"name\":\"getOwner\",\"constant\":true,\"stateMutability\":\"view\",\"payable\":false,\"inputs\":[],\"outputs\":[{\"type\":\"address\"}]},{\"type\":\"function\",\"name\":\"owner\",\"constant\":true,\"stateMutability\":\"view\",\"payable\":false,\"inputs\":[],\"outputs\":[{\"type\":\"address\"}]},{\"type\":\"function\",\"name\":\"ownerAddressToPetList\",\"constant\":true,\"stateMutability\":\"view\",\"payable\":false,\"inputs\":[{\"type\":\"address\"},{\"type\":\"uint256\"}],\"outputs\":[{\"type\":\"uint256\"}]},{\"type\":\"function\",\"name\":\"petIdxToOwnerAddress\",\"constant\":true,\"stateMutability\":\"view\",\"payable\":false,\"inputs\":[{\"type\":\"uint256\"}],\"outputs\":[{\"type\":\"address\"}]},{\"type\":\"function\",\"name\":\"petIndex\",\"constant\":true,\"stateMutability\":\"view\",\"payable\":false,\"inputs\":[],\"outputs\":[{\"type\":\"uint256\"}]},{\"type\":\"function\",\"name\":\"removeCart\",\"constant\":false,\"payable\":false,\"inputs\":[{\"type\":\"uint256\",\"name\":\"index\"}],\"outputs\":[]}]","address":"0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9","network":"localhost","deployer":"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"} +{"abi":"[{\"type\":\"constructor\",\"payable\":false,\"inputs\":[{\"type\":\"uint256\",\"name\":\"initialPetIndex\"}]},{\"type\":\"function\",\"name\":\"addPet\",\"constant\":false,\"payable\":false,\"inputs\":[],\"outputs\":[]},{\"type\":\"function\",\"name\":\"addToCart\",\"constant\":false,\"payable\":false,\"inputs\":[{\"type\":\"uint256\",\"name\":\"_petId\"},{\"type\":\"string\",\"name\":\"_petName\"},{\"type\":\"string\",\"name\":\"_petColor\"},{\"type\":\"uint256\",\"name\":\"_petPrice\"},{\"type\":\"string\",\"name\":\"_petPhoto\"}],\"outputs\":[]},{\"type\":\"function\",\"name\":\"adoptPet\",\"constant\":false,\"payable\":false,\"inputs\":[{\"type\":\"uint256\",\"name\":\"adoptIdx\"}],\"outputs\":[]},{\"type\":\"function\",\"name\":\"allAdoptedPets\",\"constant\":true,\"stateMutability\":\"view\",\"payable\":false,\"inputs\":[{\"type\":\"uint256\"}],\"outputs\":[{\"type\":\"uint256\"}]},{\"type\":\"function\",\"name\":\"getAllAdoptedPets\",\"constant\":true,\"stateMutability\":\"view\",\"payable\":false,\"inputs\":[],\"outputs\":[{\"type\":\"uint256[]\"}]},{\"type\":\"function\",\"name\":\"getAllAdoptedPetsByOwner\",\"constant\":true,\"stateMutability\":\"view\",\"payable\":false,\"inputs\":[],\"outputs\":[{\"type\":\"uint256[]\"}]},{\"type\":\"function\",\"name\":\"getCartItems\",\"constant\":true,\"stateMutability\":\"view\",\"payable\":false,\"inputs\":[],\"outputs\":[{\"type\":\"tuple[]\",\"components\":[{\"type\":\"uint256\",\"name\":\"id\"},{\"type\":\"string\",\"name\":\"name\"},{\"type\":\"string\",\"name\":\"color\"},{\"type\":\"uint256\",\"name\":\"price\"},{\"type\":\"string\",\"name\":\"photo\"}]}]},{\"type\":\"function\",\"name\":\"getCartLength\",\"constant\":true,\"stateMutability\":\"view\",\"payable\":false,\"inputs\":[],\"outputs\":[{\"type\":\"uint256\"}]},{\"type\":\"function\",\"name\":\"getOwner\",\"constant\":true,\"stateMutability\":\"view\",\"payable\":false,\"inputs\":[],\"outputs\":[{\"type\":\"address\"}]},{\"type\":\"function\",\"name\":\"owner\",\"constant\":true,\"stateMutability\":\"view\",\"payable\":false,\"inputs\":[],\"outputs\":[{\"type\":\"address\"}]},{\"type\":\"function\",\"name\":\"ownerAddressToPetList\",\"constant\":true,\"stateMutability\":\"view\",\"payable\":false,\"inputs\":[{\"type\":\"address\"},{\"type\":\"uint256\"}],\"outputs\":[{\"type\":\"uint256\"}]},{\"type\":\"function\",\"name\":\"petIdxToOwnerAddress\",\"constant\":true,\"stateMutability\":\"view\",\"payable\":false,\"inputs\":[{\"type\":\"uint256\"}],\"outputs\":[{\"type\":\"address\"}]},{\"type\":\"function\",\"name\":\"petIndex\",\"constant\":true,\"stateMutability\":\"view\",\"payable\":false,\"inputs\":[],\"outputs\":[{\"type\":\"uint256\"}]},{\"type\":\"function\",\"name\":\"removeCart\",\"constant\":false,\"payable\":false,\"inputs\":[{\"type\":\"uint256\",\"name\":\"index\"}],\"outputs\":[]}]","address":"0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512","network":"localhost","deployer":"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"} diff --git a/frontend/contract-ui/package-lock.json b/frontend/contract-ui/package-lock.json index 45be1c3..87e1e08 100644 --- a/frontend/contract-ui/package-lock.json +++ b/frontend/contract-ui/package-lock.json @@ -9,6 +9,7 @@ "version": "0.0.0", "dependencies": { "dotenv": "^16.4.5", + "email-validator": "^2.0.4", "ethers": "^5.7.2", "gsap": "^3.12.5", "moralis-v1": "^1.13.0", @@ -4359,6 +4360,14 @@ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" }, + "node_modules/email-validator": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/email-validator/-/email-validator-2.0.4.tgz", + "integrity": "sha512-gYCwo7kh5S3IDyZPLZf6hSS0MnZT8QmJFqYvbqlDZSbwdZlY6QZWxJ4i/6UhITOJ4XzyI647Bm2MXKCLqnJ4nQ==", + "engines": { + "node": ">4.0" + } + }, "node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", diff --git a/frontend/contract-ui/package.json b/frontend/contract-ui/package.json index b6e392f..f2e1642 100644 --- a/frontend/contract-ui/package.json +++ b/frontend/contract-ui/package.json @@ -10,6 +10,7 @@ }, "dependencies": { "dotenv": "^16.4.5", + "email-validator": "^2.0.4", "ethers": "^5.7.2", "gsap": "^3.12.5", "moralis-v1": "^1.13.0", diff --git a/frontend/contract-ui/src/components/PaymentCancelled.jsx b/frontend/contract-ui/src/components/PaymentCancelled.jsx new file mode 100644 index 0000000..84ce030 --- /dev/null +++ b/frontend/contract-ui/src/components/PaymentCancelled.jsx @@ -0,0 +1,23 @@ +import React from "react" +import {Link} from 'react-router-dom' + + +// *PaymentCancelledCard +const PaymentCancelledCard = () => { + //return jsx to client + return ( + <> +
+
+
+

Your payment was cancelled.

+
+
+ Back Home +
+
+
+ + ) +} +export default PaymentCancelledCard; diff --git a/frontend/contract-ui/src/components/PaymentSuccsess.jsx b/frontend/contract-ui/src/components/PaymentSuccsess.jsx index 6045f11..93ed8cf 100644 --- a/frontend/contract-ui/src/components/PaymentSuccsess.jsx +++ b/frontend/contract-ui/src/components/PaymentSuccsess.jsx @@ -1,12 +1,47 @@ -import React from 'react'; -import {Link} from 'react-router-dom' +import React, { useEffect, useState } from 'react'; +import {Link,useLocation} from 'react-router-dom' +import connect_contract from '../../helpers/connect_contract'; +import { getSigner } from '../../helpers/get_signer'; +import { useMoralis } from "react-moralis"; +import { Navbar } from './Navbar'; // *PaymentSuccessCard const PaymentSuccessCard = () => { + // Moralis + const { web3,account } = useMoralis(); + + // getPaymentSessionId + const getPaymentSessionId = () => { + const queryString = window.location.search; + const params = new URLSearchParams(queryString); + return params.get('session_id'); + } + + // removePetsFromCard + const removePetsFromCard = async () => { + if(getPaymentSessionId() != null){ + const connected_contract = await connect_contract() + const signer = getSigner(account) + const pets = await connected_contract.connect(signer).getCartItems() + pets.length>0&&pets.forEach(async (pet,index) => { + await connected_contract.connect(signer).removeCart(index) + }) + } + else{ + return null + } + } + + useEffect(()=>{ + removePetsFromCard() + },[]) + + //return jsx to client return ( <> +
}/> }/> }/> + }/> diff --git a/frontend/contract-ui/src/routes/Cart.jsx b/frontend/contract-ui/src/routes/Cart.jsx index ef0c9e2..ef04bd2 100644 --- a/frontend/contract-ui/src/routes/Cart.jsx +++ b/frontend/contract-ui/src/routes/Cart.jsx @@ -7,6 +7,8 @@ import { Navbar } from "../components/Navbar"; // !Helpers methods import connect_contract from '../../helpers/connect_contract'; import {getSigner} from '../../helpers/get_signer'; +import getStripePublishableKey from '../../helpers/init_stripe'; + // !Third part packages @@ -14,13 +16,11 @@ import { useMoralis } from "react-moralis"; import { gsap } from "gsap"; import { toast } from 'react-toastify' import { useNavigate } from "react-router-dom"; -import getStripePublishableKey from '../../helpers/init_stripe'; - - +import validator from 'email-validator'; // *Cart -export default function Cart(){ +export default function Cart(){ const [contract,setContract] = useState(null) const [provider,setProvider] = useState(window.ethereum) const [pets,setPets] = useState(null) @@ -28,6 +28,7 @@ export default function Cart(){ const [signer,setSigner] = useState() const [reload,setReload] = useState(false) const [paymentIsStart,setPaymentIsStart] = useState(false) + const [email,setEmail] = useState(null) //mavigate const navigate = useNavigate(); @@ -38,6 +39,20 @@ export default function Cart(){ //moralis const { web3,account,Moralis,isAuthenticated,user,authenticate } = useMoralis(); + + //? showMessage + const showMessage = (message_text,message_type) => { + if(message_type == "success"){ + toast.success(message_text) + } + else if(message_type == "error"){ + toast.error(message_text) + } + else if(message_type == "info"){ + toast.warning(message_text) + } + } + // ?gsap const elementRef = useRef(null); @@ -60,19 +75,25 @@ export default function Cart(){ await contract.connect(signer).removeCart(index) await removePet() if(await getCartItems()){ + setTimeout(()=>navigate("/"),0) setPaymentIsStart(true) - toast.success('Adopted Pet Successesfully') - setTimeout(()=>navigate("/"),3000) } } // ?adoptPet const adoptPet = async () => { - setPaymentIsStart(true) - pets.map(async(pet,index) => { - await contract.connect(signer).adoptPet(index) - await removeCart(index >= 1 ? index-1 : 0) - }) + const {isValidEmail,isValidPaymentMethod:ethereum} = checkPaymentCredentials(paymentOption,web3) + const isCheckedPaymentCredentials = isValidEmail && ethereum ? true : false + if(isCheckedPaymentCredentials){ + setPaymentIsStart(true) + pets.map(async(pet,index) => { + await contract.connect(signer).adoptPet(index) + await removeCart(index >= 1 ? index-1 : 0) + }) + } + else{ + setPaymentIsStart(false) + } } // ?removePet @@ -91,11 +112,43 @@ export default function Cart(){ } } + // ?validateEmail + const validateEmail = () => { + const isValidEmail = validator.validate(email); + if(!isValidEmail) { + showMessage('Invalid email address', 'error') + return false + } + return true + } + + // ?validatePaymentMethod + const validatePaymentMethod = (payment_method,wallet_public_key=null) => { + if(payment_method=='ethereum' && wallet_public_key == null){ + return null + } + else if(payment_method=='stripe' && wallet_public_key){ + const stripe = getStripePublishableKey() + return stripe + } + } + + //? checkPaymentCredentials + const checkPaymentCredentials = (payment_method,wallet_public_key=null) => {//ethereum or stripe + const isValidEmail = validateEmail() + const isValidPaymentMethod = validatePaymentMethod(payment_method, wallet_public_key) + if(isValidEmail && isValidPaymentMethod){ + return {isValidEmail,isValidPaymentMethod} + } + } + //?handlePurchase const handlePurchase = () =>{ - const stripe = getStripePublishableKey() - setPaymentIsStart(true) - fetch('http://localhost:8000/orders/create-checkout-session', { + const {isValidEmail,isValidPaymentMethod:stripe} = checkPaymentCredentials(paymentOption,true) + const isCheckedPaymentCredentials = isValidEmail && stripe ? true : false + if(isCheckedPaymentCredentials){ + setPaymentIsStart(true) + fetch('http://localhost:8000/orders/create-checkout-session', { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -112,6 +165,11 @@ export default function Cart(){ .then((res) => { console.log(res); }); + } + else{ + return null + } + } //useEffect @@ -201,7 +259,7 @@ export default function Cart(){
- + setEmail(e.target.value)} type="text" id="email" name="email" className="w-full rounded-md border border-gray-200 px-4 py-3 pl-11 text-sm shadow-sm outline-none focus:z-10 focus:border-blue-500 focus:ring-blue-500" placeholder="your.email@gmail.com" required/>