2021~_Projects/Amazon Clone

(Youtube)Amazon Clone Project(Day2)

Indie Olineoguli 2021. 4. 26. 01:23

금일 진행 사항

[Part4]The Login Page

 

  - Build up login page with CSS

 

[Part5]User Authentication
    
- Manage State of Login by redux

 

[Part6]Deploying our App
    
- Build and Deploy this App by firebase

- Username / Email in Header

 

[Part7]The Payment Page
    
- Set up payment page with CSS

 

[Part8] Payment Processing

 

Front-Side
- Set up stripe API for payment function

- Set up axios.js for using 'baseURL' as endpoint of cloud functions

- Make payment logics in Payments.js by using clientSecret

 

Back-Side

 

- Set up cloud functions(full back-end) - Serverless Application


[Part5]User Authentication - Manage State of Login and Make register function

firebase.js

import firebase from 'firebase';

const firebaseConfig = {
  apiKey: "AIzaSyDof3yqvoaz9dRNwlYfhHHX56EVyprbWS0",
  authDomain: "challenge-cc017.firebaseapp.com",
  projectId: "challenge-cc017",
  storageBucket: "challenge-cc017.appspot.com",
  messagingSenderId: "852584789925",
  appId: "1:852584789925:web:51c29467aa116ee2599a4c",
  measurementId: "G-K56QBW31E7",
};

const firebaseApp = firebase.initializeApp(firebaseConfig);

const db = firebaseApp.firestore();
const auth = firebase.auth();

export { db, auth };

Login.js

import React, { useState } from 'react'
import './Login.css'
import { Link, useHistory } from "react-router-dom"
import { auth } from './firebase'

function Login() {
  const history = useHistory();
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');

  const signIn = (e) => {
    e.preventDefault();
    //make firebase login
    auth
    .signInWithEmailAndPassword(email, password)
    .then(auth => {
      history.push('/')
    })
    .catch(error => alert(error.message))
  }

  const register = (e) => {
    e.preventDefault();
    //make firebase register
    auth
    .createUserWithEmailAndPassword(email, password)
    .then((auth) => {
      //it successfully created a new user with email and password
      // console.log(auth)
      if(auth) {
        history.push('/')
      }
    })
    .catch(error => alert(error.message))
  }

  return (
    <div className="login">
      <Link to = '/'>
        <img className="login__logo" src="https://upload.wikimedia.org/wikipedia/commons/thumb/a/a9/Amazon_logo.svg/1024px-Amazon_logo.svg.png" alt=""/>
      </Link>
      <div className="login__container">
        <h1>Sign-in</h1>
        <form action="">
          <h5>E-mail</h5>
          <input type="text" name="" id="" value={email} onChange={e => setEmail(e.target.value)}/>
          <h5>Password</h5>
          <input type="password" name="" id="" value={password} onChange={e =>setPassword(e.target.value)}/>
          <button type="submit" className='login__signInButton' onClick={signIn}>Sign In</button>
        </form>
        <p>
          By signing-in you agree to the AMAZON FAKE CLONE Conditions of Use & Sale. Please
          see our Privacy Notice, our Cookies Notice and our Interest-Based Ads Notice.
        </p>
        <button className='login__registerButton' onClick={register}>Create your Amazon account</button>
      </div>
    </div>
  )
}

export default Login

로그인 유지

App.js - auth 상태변화 감지하여 user login 상태 관리

import React, { useEffect } from 'react'
import { BrowserRouter as Router, Switch, Route } from "react-router-dom"
import { useStateValue } from './StateProvider'
import { auth } from './firebase'
import "./App.css";
import Header from './Header'
import Home from './Home'
import Checkout from './Checkout'
import Login from './Login'
import Payment from './Payment'
import Orders from './Orders'
import { Elements } from '@stripe/react-stripe-js'
import { loadStripe } from '@stripe/stripe-js';

const promise = loadStripe('pk_test_51Ik2QDKonIC4bazsf3Kn239pwoChdP7MkJgP1xcYhKCSAhjzSqGm1cE0ulRZYKU4DpNsq0QGycpWYfpgnfCFr72l0010ikyYrl')

function App() {
  const [, dispatch] = useStateValue();

  //Manage State of Log-in by Redux 
  useEffect(() => {
    auth.onAuthStateChanged(authUser => {
      if(authUser) {
        // the user just logged in / the user was logged in
        dispatch({
          type: 'SET_USER',
          user: authUser
        })  
      } else {
        // the user is logged out 
        dispatch({
          type: 'SET_USER',
          user: null
        })
      }
    })
  }, [dispatch])

  return (
    //BEM
    <Router>
      생략
    </Router>
  );
}

export default App;

Header.js - 로그아웃

import React from 'react';
import './Header.css';
import SearchIcon from '@material-ui/icons/Search';
import ShoppingBasketIcon from '@material-ui/icons/ShoppingBasket';
import { Link } from 'react-router-dom';
import { useStateValue } from './StateProvider'
import { auth } from './firebase';

function Header() {
  const [{basket, user}] = useStateValue();
  // console.log(basket.length);

  const handleAuthentication = () =>{
    if(user){
      auth.signOut();
    }
  }

  return (
    <div className="header">
     
     ...생략...
    
    </div>
  )
}

export default Header

 

reducer.js

action.user === null or specific id

export const initialState = {
  basket: [],
  user: null
}

//Selector
export const getBasketTotal = (basket) => 
  basket?.reduce((amount, item) => item.price + amount, 0);
//? function of '?'

const reducer = (state, action) => {
  // console.log(action)
  // console.log(state)
  switch(action.type) {
  
    ...생략...
    
    case 'SET_USER':
      return {
        ...state,
        user: action.user
      }

    default:
      return state;
  }
}

export default reducer;

[Part8] Payment Processing

1. Front-Side
- Set up stripe API for payment function(install stripe-related modules first)

- Set up axios.js for using 'baseURL' as endpoint of cloud functions

- Make payment logics in Payments.js by using clientSecret

 

stripe.js

payment infrastructure

 

App.js

import React, { useEffect } from 'react'
import { BrowserRouter as Router, Switch, Route } from "react-router-dom"
import { useStateValue } from './StateProvider'
import { auth } from './firebase'
import "./App.css";
import Header from './Header'
import Home from './Home'
import Checkout from './Checkout'
import Login from './Login'
import Payment from './Payment'
import Orders from './Orders'
import { Elements } from '@stripe/react-stripe-js'
import { loadStripe } from '@stripe/stripe-js';

//publishable key from stripe.com
const promise = loadStripe('pk_test_51Ik2QDKonIC4bazsf3Kn239pwoChdP7MkJgP1xcYhKCSAhjzSqGm1cE0ulRZYKU4DpNsq0QGycpWYfpgnfCFr72l0010ikyYrl')

function App() {
  const [, dispatch] = useStateValue();

  ...생략...

  return (
    //BEM
    <Router>
      <div className="app">
        <Switch>
        
         ...생략...
         
          <Route path="/payment">
            <Header />
            <Elements stripe={promise}>
              <Payment/>
            </Elements>
          </Route>
          
         ...생략...
         
        </Switch>
      </div>
    </Router>
  );
}

export default App;

Payment.js

const handleSubmit = async e => {
    e.preventDefault(); //prevent refreshing
    setProcessing(true);
    
    //clientSecret: how much money to charge
    const payload = await stripe.confirmCardPayment(clientSecret, {
      payment_method: {
        card: elements.getElement(CardElement)
      }
    }).then(({paymentIntent}) => {
      //paymentIntent = payment confirmation

      db
      .collection('users')
      .doc(user?.uid)
      .collection('orders')
      .doc(paymentIntent.id)
      .set({
        basket: basket,
        amount: paymentIntent.amount,
        created: paymentIntent.created
      })

      setSucceeded(true);
      setError(null);
      setProcessing(false);
      
      dispatch({
        type: 'EMPTY_BASKET'
      })

      history.replace('/orders') //don't come back to payment page
    })

  }

1) CardElement

 

A flexible single-line input that collects all necessary card details.

   // Get a reference to a mounted CardElement. Elements knows how
   // to find your CardElement because there can only ever be one of
   // each type of element.
   const cardElement = elements.getElement(CardElement);

2) useStripe

3) useElements

4) clientSecret

 

The client secret is a unique key returned from Stripe as part of a PaymentIntent. This key lets the client access important fields from the PaymentIntent (e.g., status) while hiding sensitive ones (e.g., amount).

  import axios from './axios';
  
  useEffect(() => {
    //generate the special stripe secret which allows us to charge a customer
    const getClientSecret = async () =>{
      const response = await axios({
        method:'post',
        //Stripe expects the total in a currencies subunits. dollars => * 100
        url: `/payments/create?total=${getBasketTotal(basket) * 100}`
      });
      // console.log(response.data.clientSecret);
      setClientSecret(response.data.clientSecret);
    }

    getClientSecret();
  }, [basket]);

axios.js

import axios from 'axios'

const instance = axios.create({
  baseURL: "https://us-central1-challenge-cc017.cloudfunctions.net/api" // THE API(cloud function) URL
  // baseURL: "http://localhost:5001/challenge-cc017/us-central1/api" // THE API Emulater URL
});

export default instance;

baseURL ? 파이어베이스 클라우드 서버의 URL

functions - index.js (파이어베이스 클라우드 서버)

require("dotenv").config();
const functions = require("firebase-functions");
const express = require("express");
const cors = require("cors");
const stripe = require("stripe")(process.env.STRIPE_SECRET_KEY);

// API
// App config
const app = express();
// Middlewares
app.use(cors({origin: true}));
app.use(express.json());
// API routes
app.get("/", (req, res) => res.status(200).send("hello world"));
app.post("/payments/create", async (req, res) => {
  const total = req.query.total;

  console.log("Payment Request Received!! >>> ", total);

  const paymentIntent = await stripe.paymentIntents.create({
    amount: total,
    currency: "usd",
  });
  // Ok - Created
  // console.log(paymentIntent);
  res.status(201).send({
    clientSecret: paymentIntent.client_secret,
  });
});

// Listen command
exports.api = functions.https.onRequest(app);

// Example Endpoint
// http://localhost:5001/challenge-cc017/us-central1/api

 

5) paymentIntent

 

A PaymentIntent guides you through the process of collecting a payment from your customer. We recommend that you create exactly one PaymentIntent for each order or customer session in your system. You can reference the PaymentIntent later to see the history of payment attempts for a particular session.

 

paymentIntent Objects

{
  "id": "pi_1DoRpv2eZvKYlo2CpdwZmJrT",
  "object": "payment_intent",
  "amount": 1099,
  "amount_capturable": 0,
  "amount_received": 0,
  "application": null,
  "application_fee_amount": null,
  "canceled_at": null,
  "cancellation_reason": null,
  "capture_method": "automatic",
  "charges": {
    "object": "list",
    "data": [],
    "has_more": false,
    "url": "/v1/charges?payment_intent=pi_1DoRpv2eZvKYlo2CpdwZmJrT"
  },
  "client_secret": "pi_1DoRpv2eZvKYlo2CpdwZmJrT_secret_yfTgUAdOksvMkOCKQS6wyLy0w",
  "confirmation_method": "automatic",
  "created": 1546505155,
  "currency": "eur",
  "customer": null,
  "description": null,
  "invoice": null,
  "last_payment_error": null,
  "livemode": false,
  "metadata": {},
  "next_action": null,
  "on_behalf_of": null,
  "payment_method": null,
  "payment_method_options": {},
  "payment_method_types": [
    "card"
  ],
  "receipt_email": null,
  "review": null,
  "setup_future_usage": null,
  "shipping": null,
  "statement_descriptor": null,
  "statement_descriptor_suffix": null,
  "status": "requires_payment_method",
  "transfer_data": null,
  "transfer_group": null
}

 

CurrencyFormat(module/library)

   <CurrencyFormat
     renderText={(value) => (
       <h3>
       Order Total: {value}
       </h3>
     )}
     decimalScale={2}
     value={getBasketTotal(basket)}
     displayType={"text"}
     thousandSeparator={true}
     prefix={"$"}
   />

For more detail: www.npmjs.com/package/react-currency-format

 

 

2. Back-Side

 

- Set up cloud functions(full back-end) - Serverless Application

require("dotenv").config();
const functions = require("firebase-functions");
const express = require("express");
const cors = require("cors");
const stripe = require("stripe")(process.env.STRIPE_SECRET_KEY);

// API
// App config
const app = express();
// Middlewares
app.use(cors({origin: true}));
app.use(express.json());
// API routes
app.get("/", (req, res) => res.status(200).send("hello world"));
app.post("/payments/create", async (req, res) => {
  const total = req.query.total;

  console.log("Payment Request Received!! >>> ", total);

  const paymentIntent = await stripe.paymentIntents.create({
    amount: total,
    currency: "usd",
  });
  // Ok - Created
  // console.log(paymentIntent);
  res.status(201).send({
    clientSecret: paymentIntent.client_secret,
  });
});

// Listen command
exports.api = functions.https.onRequest(app);

// Example Endpoint
// http://localhost:5001/challenge-cc017/us-central1/api

 

dashboard.stripe.com

payments 기록을 확인할 수 있다.