Front-end pour Stripe
This commit is contained in:
		| @@ -11,6 +11,8 @@ | ||||
|         "@fortawesome/fontawesome-svg-core": "^6.2.0", | ||||
|         "@fortawesome/free-solid-svg-icons": "^6.2.0", | ||||
|         "@fortawesome/react-fontawesome": "^0.2.0", | ||||
|         "@stripe/react-stripe-js": "^1.15.0", | ||||
|         "@stripe/stripe-js": "^1.44.1", | ||||
|         "@testing-library/jest-dom": "^5.16.5", | ||||
|         "@testing-library/react": "^13.4.0", | ||||
|         "@testing-library/user-event": "^13.5.0", | ||||
| @@ -3395,6 +3397,24 @@ | ||||
|         "@sinonjs/commons": "^1.7.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@stripe/react-stripe-js": { | ||||
|       "version": "1.15.0", | ||||
|       "resolved": "https://registry.npmjs.org/@stripe/react-stripe-js/-/react-stripe-js-1.15.0.tgz", | ||||
|       "integrity": "sha512-nqIOuAbbAN1p/zj2d2vykMzd097ZiHbu0+EpPRcfiswBRQIQIZaPrUwmj6HGD8BRA6Wp89mZen1UJbqtsfc3ZA==", | ||||
|       "dependencies": { | ||||
|         "prop-types": "^15.7.2" | ||||
|       }, | ||||
|       "peerDependencies": { | ||||
|         "@stripe/stripe-js": "^1.44.1", | ||||
|         "react": "^16.8.0 || ^17.0.0 || ^18.0.0", | ||||
|         "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@stripe/stripe-js": { | ||||
|       "version": "1.44.1", | ||||
|       "resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-1.44.1.tgz", | ||||
|       "integrity": "sha512-DKj3U6tS+sCNsSXsoZbOl5gDrAVD3cAZ9QCiVSykLC3iJo085kkmw/3BAACRH54Bq2bN34yySuH6G1SLh2xHXA==" | ||||
|     }, | ||||
|     "node_modules/@surma/rollup-plugin-off-main-thread": { | ||||
|       "version": "2.2.3", | ||||
|       "resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz", | ||||
| @@ -21007,6 +21027,19 @@ | ||||
|         "@sinonjs/commons": "^1.7.0" | ||||
|       } | ||||
|     }, | ||||
|     "@stripe/react-stripe-js": { | ||||
|       "version": "1.15.0", | ||||
|       "resolved": "https://registry.npmjs.org/@stripe/react-stripe-js/-/react-stripe-js-1.15.0.tgz", | ||||
|       "integrity": "sha512-nqIOuAbbAN1p/zj2d2vykMzd097ZiHbu0+EpPRcfiswBRQIQIZaPrUwmj6HGD8BRA6Wp89mZen1UJbqtsfc3ZA==", | ||||
|       "requires": { | ||||
|         "prop-types": "^15.7.2" | ||||
|       } | ||||
|     }, | ||||
|     "@stripe/stripe-js": { | ||||
|       "version": "1.44.1", | ||||
|       "resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-1.44.1.tgz", | ||||
|       "integrity": "sha512-DKj3U6tS+sCNsSXsoZbOl5gDrAVD3cAZ9QCiVSykLC3iJo085kkmw/3BAACRH54Bq2bN34yySuH6G1SLh2xHXA==" | ||||
|     }, | ||||
|     "@surma/rollup-plugin-off-main-thread": { | ||||
|       "version": "2.2.3", | ||||
|       "resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz", | ||||
|   | ||||
| @@ -6,6 +6,8 @@ | ||||
|     "@fortawesome/fontawesome-svg-core": "^6.2.0", | ||||
|     "@fortawesome/free-solid-svg-icons": "^6.2.0", | ||||
|     "@fortawesome/react-fontawesome": "^0.2.0", | ||||
|     "@stripe/react-stripe-js": "^1.15.0", | ||||
|     "@stripe/stripe-js": "^1.44.1", | ||||
|     "@testing-library/jest-dom": "^5.16.5", | ||||
|     "@testing-library/react": "^13.4.0", | ||||
|     "@testing-library/user-event": "^13.5.0", | ||||
|   | ||||
| @@ -0,0 +1,105 @@ | ||||
| import React, { useState } from "react" | ||||
| import { CardElement, useElements, useStripe } from "@stripe/react-stripe-js" | ||||
| import { Button } from "react-bootstrap" | ||||
|  | ||||
| const CARD_OPTIONS = { | ||||
|     iconStyle: "solid", | ||||
|     style: { | ||||
|         base: { | ||||
|             iconColor: "black", | ||||
|             color: "white", | ||||
|             fontWeight: 500, | ||||
|             fontFamily: "Roboto, Open Sans, Segoe UI, sans-serif", | ||||
|             fontSize: "16px", | ||||
|             fontSmoothing: "antialiased", | ||||
|             ":-webkit-autofill": { color: "beige" }, | ||||
|             "::placeholder": { color: "lightgray" } | ||||
|         }, | ||||
|         invalid: { | ||||
|             iconColor: "red", | ||||
|             color: "red" | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
|  | ||||
| const PaymentForm = ({ cost }) => { | ||||
|  | ||||
|     const [success, setSuccess] = useState(false); | ||||
|     const stripe = useStripe(); | ||||
|     const elements = useElements(); | ||||
|  | ||||
|     const handleSubmit = async (e) => { | ||||
|         e.preventDefault(); | ||||
|         const { error, paymentMethod } = await stripe.createPaymentMethod({ | ||||
|             type: "card", | ||||
|             card: elements.getElement(CardElement) | ||||
|         }) | ||||
|  | ||||
|         if (cost <= 0) { | ||||
|             console.log("Coût invalide: ", cost); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         if (!error) { | ||||
|             const { id } = paymentMethod; | ||||
|  | ||||
|             const json = JSON.stringify({ amount: cost, stripeId: id }); | ||||
|  | ||||
|  | ||||
|             fetch(`https://localhost:7292/api/Payment`, { | ||||
|                 method: 'POST', | ||||
|                 credentials: 'include', | ||||
|                 mode: 'cors', | ||||
|                 headers: { | ||||
|                     'Accept': 'application/json', | ||||
|                     'Content-Type': 'application/json' | ||||
|                 }, | ||||
|                 body: json | ||||
|             }).then((response) => { | ||||
|                 if (response.ok) { | ||||
|                     console.log("Successful payment"); | ||||
|                     setSuccess(true); | ||||
|                 } | ||||
|                 else { | ||||
|                     console.log(response); | ||||
|                 } | ||||
|             }).catch((error) => { | ||||
|                 console.log("Error: ", error); | ||||
|             }) | ||||
|  | ||||
|         } | ||||
|         else { | ||||
|             console.log(error.message); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|  | ||||
|     return ( | ||||
|         <> | ||||
|             {!success ? | ||||
|                 <form onSubmit={handleSubmit}> | ||||
|                     <fieldset className="FormGroup"> | ||||
|                         <div className="FormRow"> | ||||
|                             <CardElement options={CARD_OPTIONS} /> | ||||
|                         </div> | ||||
|                     </fieldset> | ||||
|                     <div className="Payment-btn-container"> | ||||
|                         <Button className="Payment-btn" type="submit">Payer</Button> | ||||
|                     </div> | ||||
|                 </form> | ||||
|                 : | ||||
|                 <div> | ||||
|                     <h2> | ||||
|                         L'achat s'est déroulé avec succès | ||||
|                     </h2> | ||||
|                 </div> | ||||
|             } | ||||
|         </> | ||||
|     ) | ||||
|  | ||||
| } | ||||
|  | ||||
| export default PaymentForm; | ||||
| @@ -0,0 +1,19 @@ | ||||
| import React from "react"; | ||||
| import { loadStripe } from "@stripe/stripe-js" | ||||
| import { Elements } from "@stripe/react-stripe-js" | ||||
| import PaymentForm from "./PaymentForm"; | ||||
|  | ||||
| const PUBLIC_KEY = "pk_test_51M8mzOEerenEZcQIUmJIrmsaZeeNlOil2G1JcMvvO68w50MJr8rDwUjVO44a8dDhSlsRH4GdzH9rDqtkg4Rtbzco00NqkHdn3H"; | ||||
|  | ||||
| const stripeTestPromise = loadStripe(PUBLIC_KEY); | ||||
|  | ||||
| const StripeContainer = ({cost}) => { | ||||
|     return ( | ||||
|         <Elements stripe={stripeTestPromise}> | ||||
|             <PaymentForm cost={cost}/> | ||||
|         </Elements> | ||||
|     ) | ||||
| } | ||||
|  | ||||
|  | ||||
| export default StripeContainer; | ||||
| @@ -5,9 +5,6 @@ export function TotalProductsPrice(/*{ products }*/) { | ||||
|  | ||||
|     const cart = useContext(CartContext); | ||||
|  | ||||
|     // const productTotal = (p) => { | ||||
|     //     return (p.quantity * (p.product.status == 3 || p.product.status == 4 ? p.product.promoPrice : p.product.price)) | ||||
|     // } | ||||
|  | ||||
|     const getPriceHTML = (/*prods*/) => { | ||||
|  | ||||
| @@ -19,9 +16,6 @@ export function TotalProductsPrice(/*{ products }*/) { | ||||
|         var tvq = 0; | ||||
|         var total; | ||||
|  | ||||
|         // prods.map((p) => { | ||||
|         //     price += productTotal(p); | ||||
|         // }); | ||||
|  | ||||
|         tps = price * (tpsRate / 100); | ||||
|         tvq = price * (tvqRate / 100); | ||||
|   | ||||
| @@ -7,6 +7,7 @@ import { useNavigate } from "react-router-dom"; | ||||
| import Swal from "sweetalert2"; | ||||
| import withReactContent from "sweetalert2-react-content"; | ||||
| import { CartContext } from "../components/Cart"; | ||||
| import StripeContainer from "../components/StripeContainer" | ||||
|  | ||||
| const ReviewInvoice = () => { | ||||
|  | ||||
| @@ -28,26 +29,38 @@ const ReviewInvoice = () => { | ||||
|         province: "", | ||||
|         country: "", | ||||
|         postalCode: "", | ||||
|         prodQuant: [] | ||||
|         prodQuant: [], | ||||
|     }); | ||||
|  | ||||
|     const [total, setTotal] = useState(-1); | ||||
|  | ||||
|     useEffect(() => { | ||||
|         const cookies = new Cookies(); | ||||
|         const thisInvoice = cookies.get('invoice'); | ||||
|         if (thisInvoice != null) { | ||||
|             var dic = {}; | ||||
|             if (cart.items.length > 0) { | ||||
|                 cart.items.forEach((item) => | ||||
|                     dic[item.id] = item.quantity | ||||
|                 cart.items.forEach((item) => { | ||||
|                     dic[item.id] = item.quantity; | ||||
|                 } | ||||
|                 ) | ||||
|             } | ||||
|  | ||||
|             async function test() { | ||||
|             const tpsRate = 5; | ||||
|             const tvqRate = 9.975; | ||||
|  | ||||
|                 const json = JSON.stringify(dic); | ||||
|                 console.log(await json); | ||||
|             } | ||||
|             test(); | ||||
|             var price = cart.getTotalCost(); | ||||
|             var tps = 0; | ||||
|             var tvq = 0; | ||||
|             var totalCost; | ||||
|  | ||||
|             tps = price * (tpsRate / 100); | ||||
|             tvq = price * (tvqRate / 100); | ||||
|             totalCost = price + tps + tvq; | ||||
|             totalCost = totalCost * 100; | ||||
|             totalCost = totalCost.toFixed(0); | ||||
|  | ||||
|             setTotal(totalCost); | ||||
|  | ||||
|  | ||||
|             setThisInvoice((e) => { | ||||
| @@ -73,65 +86,12 @@ const ReviewInvoice = () => { | ||||
|         } | ||||
|     }, [cart]); | ||||
|  | ||||
|  | ||||
|     // const handleAddOne = (id) => { | ||||
|  | ||||
|     //     var modifiedPQ = prodQuant.filter((pq) => pq.id == id); | ||||
|     //     var modifiedProd = prodQuant.filter((pq) => pq.product.id == id); | ||||
|  | ||||
|     //     modifiedPQ.quantity++; | ||||
|     //     modifiedProd.quantity = modifiedPQ.quantity; | ||||
|  | ||||
|     //     setProdQuant([...(prodQuant.filter((pq) => pq.id !== id)), { ...modifiedPQ }].sort((a, b) => a.id - b.id)); | ||||
|     //     setProducts([...(products.filter((pq) => pq.product.id !== id)), { ...modifiedProd }].sort((a, b) => a.product.id - b.product.id)); | ||||
|  | ||||
|  | ||||
|     // } | ||||
|  | ||||
|     // const handleRemoveOne = (id) => { | ||||
|     //     var modifiedPQ = prodQuant.filter((pq) => pq.id == id); | ||||
|     //     var modifiedProd = prodQuant.filter((pq) => pq.product.id == id); | ||||
|  | ||||
|     //     if (modifiedPQ.quantity - 1 <= 0) { | ||||
|     //         setProdQuant([...(prodQuant.filter((pq) => pq.id !== id))].sort((a, b) => a.id - b.id)); | ||||
|     //         setProducts([...(products.filter((pq) => pq.product.id !== id))].sort((a, b) => a.product.id - b.product.id)); | ||||
|     //     } | ||||
|     //     else { | ||||
|     //         modifiedPQ.quantity--; | ||||
|     //         modifiedProd.quantity = modifiedPQ.quantity; | ||||
|     //         setProdQuant([...(prodQuant.filter((pq) => pq.id !== id)), { ...modifiedPQ }].sort((a, b) => a.id - b.id)); | ||||
|     //         setProducts([...(products.filter((pq) => pq.product.id !== id)), { ...modifiedProd }].sort((a, b) => a.product.id - b.product.id)); | ||||
|     //     } | ||||
|     // } | ||||
|  | ||||
|     // const handleRemoveProduct = (id) => { | ||||
|  | ||||
|     //     setProdQuant([...(prodQuant.filter((pq) => pq.id !== id))].sort((a, b) => a.id - b.id)); | ||||
|     //     setProducts([...(products.filter((pq) => pq.product.id !== id))].sort((a, b) => a.product.id - b.product.id)); | ||||
|     // } | ||||
|  | ||||
|     const handleModify = () => { | ||||
|         navigate("/formulaire") | ||||
|     } | ||||
|  | ||||
|     const handleConfirmer = async () => { | ||||
|  | ||||
|         // var dic = {}; | ||||
|         // if (cart.items.length > 0) { | ||||
|         //     cart.items.forEach((item) => | ||||
|         //         dic[item.id] = item.quantity | ||||
|         //     ) | ||||
|         // } | ||||
|         // const json = JSON.stringify(dic); | ||||
|         // console.log(await json); | ||||
|  | ||||
|         // const json1 = JSON.stringify(cart.items); | ||||
|         // console.log(await json1); | ||||
|  | ||||
|         //setThisInvoice({ ...thisInvoice, prodQuant: dic }); | ||||
|  | ||||
|         const json = JSON.stringify(thisInvoice); | ||||
|         console.log(json); | ||||
|  | ||||
|         const response = await fetch(`https://localhost:7292/api/Invoice`, { | ||||
|             method: 'POST', | ||||
| @@ -167,69 +127,73 @@ const ReviewInvoice = () => { | ||||
|     } | ||||
|  | ||||
|     return ( | ||||
|         <Row> | ||||
|             <h2 className="confirmer-infos">Veuillez confirmer les informations ci-dessous!</h2> | ||||
|             <Col xs={6} md={4}> | ||||
|         <> | ||||
|             <Row> | ||||
|                 <h2 className="confirmer-infos">Veuillez confirmer les informations ci-dessous!</h2> | ||||
|                 <Col xs={6} md={4}> | ||||
|  | ||||
|                 <div className="review-invoice-info"> | ||||
|                     <div> | ||||
|                         <label className="bold">Prénom: </label> <label>{thisInvoice.firstName}</label> | ||||
|                     </div> | ||||
|                     <div> | ||||
|                         <label className="bold">Nom: </label> <label>{thisInvoice.lastName}</label> | ||||
|                     </div> | ||||
|                     <div> | ||||
|                         <label className="bold">Téléphone: </label> <label>{thisInvoice.phoneNumber}</label> | ||||
|                     </div> | ||||
|                     <div> | ||||
|                         <label className="bold">Courriel: </label> <label>{thisInvoice.emailAddress}</label> | ||||
|                     </div> | ||||
|                 </div> | ||||
|  | ||||
|                 <div className="review-invoice-address-container"> | ||||
|                     <div> | ||||
|                         <label className="bold">Numéro civique: </label> <label>{thisInvoice.civicNumber}</label> | ||||
|                     </div> | ||||
|                     {thisInvoice.appartment != null ? | ||||
|                     <div className="review-invoice-info"> | ||||
|                         <div> | ||||
|                             <label className="bold">Numéro d'appartement: </label> <label>{thisInvoice.appartment}</label> | ||||
|                             <label className="bold">Prénom: </label> <label>{thisInvoice.firstName}</label> | ||||
|                         </div> | ||||
|                         <div> | ||||
|                             <label className="bold">Nom: </label> <label>{thisInvoice.lastName}</label> | ||||
|                         </div> | ||||
|                         <div> | ||||
|                             <label className="bold">Téléphone: </label> <label>{thisInvoice.phoneNumber}</label> | ||||
|                         </div> | ||||
|                         <div> | ||||
|                             <label className="bold">Courriel: </label> <label>{thisInvoice.emailAddress}</label> | ||||
|                         </div> | ||||
|                         : null | ||||
|                     } | ||||
|                     <div> | ||||
|                         <label className="bold">Rue: </label> <label>{thisInvoice.street}</label> | ||||
|                     </div> | ||||
|                     <div> | ||||
|                         <label className="bold">Ville: </label> <label>{thisInvoice.city}</label> | ||||
|  | ||||
|                     <div className="review-invoice-address-container"> | ||||
|                         <div> | ||||
|                             <label className="bold">Numéro civique: </label> <label>{thisInvoice.civicNumber}</label> | ||||
|                         </div> | ||||
|                         {thisInvoice.appartment != null ? | ||||
|                             <div> | ||||
|                                 <label className="bold">Numéro d'appartement: </label> <label>{thisInvoice.appartment}</label> | ||||
|                             </div> | ||||
|                             : null | ||||
|                         } | ||||
|                         <div> | ||||
|                             <label className="bold">Rue: </label> <label>{thisInvoice.street}</label> | ||||
|                         </div> | ||||
|                         <div> | ||||
|                             <label className="bold">Ville: </label> <label>{thisInvoice.city}</label> | ||||
|                         </div> | ||||
|                         <div> | ||||
|                             <label className="bold">Province (abréviation): </label> <label>{thisInvoice.province}</label> | ||||
|                         </div> | ||||
|                         <div> | ||||
|                             <label className="bold">Pays: </label> <label>{thisInvoice.country}</label> | ||||
|                         </div> | ||||
|                         <div> | ||||
|                             <label className="bold">Code postal: </label> <label>{thisInvoice.postalCode}</label> | ||||
|                         </div> | ||||
|                     </div> | ||||
|                     <div> | ||||
|                         <label className="bold">Province (abréviation): </label> <label>{thisInvoice.province}</label> | ||||
|                     <div className="review-invoice-info"> | ||||
|                         <TotalProductsPrice | ||||
|                         /> | ||||
|                     </div> | ||||
|                     <div> | ||||
|                         <label className="bold">Pays: </label> <label>{thisInvoice.country}</label> | ||||
|                     </div> | ||||
|                     <div> | ||||
|                         <label className="bold">Code postal: </label> <label>{thisInvoice.postalCode}</label> | ||||
|                     </div> | ||||
|                 </div> | ||||
|                 <div className="review-invoice-info"> | ||||
|                     <TotalProductsPrice | ||||
|                     /> | ||||
|                 </div> | ||||
|                 <Row className="invoice-buttons"> | ||||
|                     {/* <Row className="invoice-buttons"> | ||||
|                     <Col xs={6}> | ||||
|                         <Button className="invoice-button confirmer" onClick={handleConfirmer}>Confirmer</Button> | ||||
|                     </Col> | ||||
|                     <Col xs={6}> | ||||
|                         <Button className="invoice-button modifier" onClick={handleModify}>Modifier</Button> | ||||
|                     </Col> | ||||
|                 </Row> | ||||
|             </Col> | ||||
|             <Col xs={6} md={8}> | ||||
|                 <ReviewProdList | ||||
|                 /> | ||||
|             </Col> | ||||
|         </Row> | ||||
|                 </Row> */} | ||||
|                 </Col> | ||||
|                 <Col xs={6} md={8}> | ||||
|                     <ReviewProdList | ||||
|                     /> | ||||
|                 </Col> | ||||
|             </Row> | ||||
|             <StripeContainer cost={total} /> | ||||
|         </> | ||||
|  | ||||
|     ); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -20,6 +20,44 @@ a { | ||||
|     text-decoration: none; | ||||
| } | ||||
|  | ||||
| .Payment-btn-container { | ||||
|     display: flex; | ||||
| } | ||||
|  | ||||
| .Payment-btn { | ||||
|     margin: auto; | ||||
|     width: 20%; | ||||
| } | ||||
|  | ||||
| .FormGroup { | ||||
|     margin: 20px 15px 20px; | ||||
|     padding: 0; | ||||
|     border-style: none; | ||||
|     color: beige !important; | ||||
|     background-color: purple; | ||||
|     will-change: opacity, transform; | ||||
|     border-radius: 4px; | ||||
| } | ||||
|  | ||||
| .FormRow { | ||||
|     display: -ms-flexbox; | ||||
|     display: flex; | ||||
|     -ms-flex-align: center; | ||||
|     align-items: center; | ||||
|     margin-left: 15px; | ||||
| } | ||||
|  | ||||
| .StripeElement--webkit-autofill { | ||||
|     background: transparent !important; | ||||
| } | ||||
|  | ||||
| .StripeElement { | ||||
|     width: 100%; | ||||
|     padding: 11px 15px 11px 0; | ||||
| } | ||||
|  | ||||
|  | ||||
|  | ||||
| .Contact { | ||||
|     background-color: beige; | ||||
|     text-align: center; | ||||
| @@ -770,7 +808,7 @@ a { | ||||
|  | ||||
| .invoice-item-products h3 { | ||||
|     text-align: center !important; | ||||
|     color:purple; | ||||
|     color: purple; | ||||
|     font-weight: bold; | ||||
|     background-color: beige; | ||||
|     width: auto; | ||||
| @@ -831,12 +869,12 @@ a { | ||||
| } | ||||
|  | ||||
| .invoice-buttons { | ||||
|     display:flex; | ||||
|     margin:5%; | ||||
|     display: flex; | ||||
|     margin: 5%; | ||||
| } | ||||
|  | ||||
| .invoice-button { | ||||
|     margin:2%; | ||||
|     margin: 2%; | ||||
|     width: 100%; | ||||
|     align-content: center; | ||||
| } | ||||
| @@ -851,7 +889,7 @@ a { | ||||
|  | ||||
| .confirmer-infos { | ||||
|     text-align: center; | ||||
|     color:white; | ||||
|     color: white; | ||||
|     font-weight: bold; | ||||
| } | ||||
|  | ||||
| @@ -861,6 +899,12 @@ a { | ||||
| /* -------------------------------------------------------- */ | ||||
| @media(max-width:900px) { | ||||
|  | ||||
|     .Payment-btn { | ||||
|         margin-top: 10px; | ||||
|         height: 70px; | ||||
|         width: 80%; | ||||
|     } | ||||
|  | ||||
|     .invoice-item-container { | ||||
|         display: block; | ||||
|     } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user