본문 바로가기

react-shop-web/front & back

Paypal로 결제 성공 후 할 일들

  • 카트를 비우기
  • 결제 정보 저장하기 
    • Payment Collection (Detailed)
    • User Collection (Simple)
더보기
import React, {useEffect, useState} from 'react'
import {useDispatch} from 'react-redux';
import {getCartItems, removeCartItem, onSuccessBuy} from '../../../_actions/user_actions';
import UserCardBlock from './Sections/UserCardBlock';
import {Empty, Result} from 'antd'
import Paypal from '../../utils/Paypal';

function CartPage(props) {
  const dispatch = useDispatch();

  const [Total, setTotal] = useState(0);
  const [ShowTotal, setShowTotal] = useState(false)
  const [showSuccess, setshowSuccess] = useState(false)

  useEffect(() => {

    let cartItems = []
    
    // 리덕스 user state에서 cart안에 상품이 들어있는지 확인 
    if(props.user.userData && props.user.userData.cart) {
      // 카트 안에 상품이 하나 이상 들어있다면 
      if(props.user.userData.cart.length > 0){
        props.user.userData.cart.forEach(item => {
          cartItems.push(item.id)
        })

        // 밑에 액션에 매개변수로 방금 props로부터 받은 user의 userData에서 cart들을 담은 변수와,
        // userData의 cart 정보도 주자. (이때 두번째 매개변수를 넣어주는 이유는 quantity는 user의 cart 부분에만 존재하기 때문)
        dispatch(getCartItems(cartItems, props.user.userData.cart))
          .then(response => {
              calculateTotal(response.payload)
          })
      }
    }
  
    return () => {
      
    }
  }, [props.user.userData])
  
  const calculateTotal = (cartDetail) => {
    let total = 0;

    cartDetail.map(item => {
      total += parseInt(item.price) * item.quantity
    })

    setTotal(total);
    setShowTotal(true);
  }

  let removeFromCart = (productId) => {

    dispatch(removeCartItem(productId))
      .then(response => {
        if(response.payload.productInfo.length <= 0){
          setShowTotal(false);
        }
        else {
          calculateTotal(response.payload.productInfo)
        }
      })
  }

  // 매개 변수 값으로 받은 data는 Paypal.js에서 onSuccess 함수의 payment값이다. 
  const transactionSuccess = (data) => {

    dispatch(onSuccessBuy({
      paymentData: data,
      cartDetail: props.user.cartDetail
    }))
    .then(response => {
      if(response.payload.success) {
        setShowTotal(false)
        setshowSuccess(true)
      }
    })

  }


  return (
    <div style={{ width: '85%', margin: '3rem auto'}}>
      <h1>My Cart</h1>
      <div>
        <UserCardBlock products={props.user.cartDetail && props.user.cartDetail} removeItem={removeFromCart}/>  
      </div>

      {ShowTotal ? 
        <div style={{marginTop: '3rem'}}>
          <h2>Total Amount: ${Total}</h2>
          <Paypal 
            total={Total}
            onSuccess={transactionSuccess}
          />
        </div>
      :
      showSuccess ? 
          <Result
            status="success"
            title="Successfully Purchased Items"
          />
          :
        <>
          <br/>
          <Empty description={false}/>
        </>
      }
    </div>
  )
}

export default CartPage

CartPage.js

 

 

export function onSuccessBuy(data){
    const request = axios.post(`/api/users/successBuy`, data)
        .then(response => {response => response.data});

    return {
        type: ON_SUCCESS_BUY,
        payload: request
    }
}

user_actions.js

 

        case ON_SUCCESS_BUY:
            return {...state, cartDetail: action.payload.cartDetail,
                userData: {
                    ...state.userData, cart: action.payload.cart
                }
            }

user_reducer.js

 

 

더보기
router.post("/successBuy", auth, (req,res) => {

    // 1. User Collection 안에 history 필드 안에 간단한 결제 정보 넣어주기 
    //     ㄴ dateOfPurchase, name, id, price, quantity, paymentId
    let history = [];
    let transactionData = {};

    console.log(req.body);

    req.body.cartDetail.forEach(item => {
        history.push({
            dateOfPurchase: Date.now(),
            name: item.title,
            id: item._id,
            price: item.price,
            quantity: item.quantity,
            // paymentID는 Paypal.js에서 onSuccess 함수의 매개변수 값에 들어있다. 
            paymentId: req.body.paymentData.paymentID
        })
    })
    
    // 2. Payment collection 안에 자세한 결제 정보 넣어주기 
    transactionData.user = {
        // 해당 데이터들은 auth 미들웨어를 통해 얻은 정보를 넣어주는거임
        id: req.user._id,
        name: req.user.name,
        email: req.user.email
    }

    transactionData.data = req.body.paymentData
    transactionData.product = history

        // history 정보 저장 
        User.findOneAndUpdate(
            {_id: req.user._id},
            // 결제가 완료되면 카트에 있던 것들을 지워줘야함 그래서 set 문법으로 카트를 지워주자    
            {$push: {history: history}, $set: {cart: []}},
            // userCollection에 새롭게 업데이트된 doc를 받을 수 있게 밑의 코드도 넣어주자 
            {new: true},
            (err, user) => {
                if(err) return res.json({ success: false, err})

                // payment에다가 transaction 정보 저장 
                const payment = new Payment(transactionData)
                payment.save((err, doc) => {
                    if(err) return res.json({ success: false, err})

                    // 3. Product Collection 안에 있는 sold 필드 정보 업데이트 시켜주기 
                    
                    // 상품 당 몇 개의 quantity를 샀는지
                    let products = [];
                    doc.product.forEach(item => {
                        products.push({ id: item.id, quantity: item.quantity})
                    })

                    async.eachSeries(products, (item, callback) => {
                        
                        Product.update(
                            {_id: item.id},
                            {
                                $inc: {
                                    "sold" : item.quantity 
                                }
                            },
                            // 원래라면 업데이트된 doc을 frontEnd에다가 보내줘야하기 때문에 {new: true}를 넣어줬었다.
                            // 이번에는 굳이 front에 보내주지 않아도 되므로 {new: false}를 넣어주자.
                            {new: false},
                            callback
                        )
                    }, (err) => {
                        if(err) return res.status(400).json({ success: false, err})
                        // 결제가 성공했다면 카트를 다 비워줬기 때문에 cartDetail은 비어있을 것이다.
                        res.status(200).json({ success: true, cart: user.cart, cartDetail: []})
                    })

                })
            }
        )  
})

server/routes/users.js

 

 

위 과정은 다음과 같다.

  1. Paypal 컴포넌트에서 결제가 성공했다면 부모 컴포넌트(CartPage.js)로 부터 받은 onSuccess함수를 통해 매개변수로 결제에 성공하면서 얻은 payment값과 함께 넣어주면서 부모 컴포넌트의 transactionSuccess 함수를 호출한다.
  2. CartPage.js에서 호출된 transactionSuccess 함수는 onSuccessBuy액션을 dispatch을 이용해서 reducer에 전달한다.
  3. onSuccessBuy 액션에서는 '/api/users/successBut' api에 post로 데이터를 요청한다. 액션에서 서버에 요청할 때 준 data값에는 payment값과 cartDetail이 들어있다.
  4. 서버의 '/successBuy' 라우트에서 해줄 일은 두 가지이다.  
    1. User Collection 에 있는 history 필드 안에 간단한 결제 정보 넣어주기(dateOfPurchase, name, id, price, quantity, paymentId)
    2. Payment collection 안에 자세한 결제 정보 넣어주기 
  5.  전 게시글에서도 말했었듯이 Payment Model에는 user, data, product 필드가 존재한다. 위 코드에서 transactionData 객체를 만들어서 넣어주는 값들이 세 가지에 해당한다. history배열과 transactionData객체를 Payment DB에 새로 저장해준다.

 

위 코드에서 aynsc.eachSeries라는 문법을 사용했는데 이는 for문으로 일일이 조건 따지면서 DB를 업데이트 하지 않고 eachSeries 함수를 이용하여 보다 더 간결하게 코드를 짜기위해 사용했다.

root 디렉토리에서 다운받아주자.

npm install async --save

https://www.npmjs.com/package/async

 

async

Higher-order functions and common patterns for asynchronous code. Latest version: 3.2.4, last published: 8 months ago. Start using async in your project by running `npm i async`. There are 32616 other projects in the npm registry using async.

www.npmjs.com

 

 

 

ant design에서 Result를 갖고와서 결제가 성공했다면 띄워줄 수 있는 문구를 넣어줬다. 

https://ant.design/components/result

 

Result - Ant Design

Please check and modify the following information before resubmitting.

ant.design