본문 바로가기

React

카카오페이 결제

 

import React from 'react';
import { useNavigate } from 'react-router-dom';
import {
    MainContainer,
    LeftContainer,
    Logo,
    P,
    Button,
    RightContainer,
    Navbar,
    NavbarBtn,
    SponserMoney,
    InputTag,
    Body,
    FundingDiv,
    SponserDiv,
    SponserComment,
    SponsorImg,
    TogetherDiv,
} from './FundingPayStyles';
import CheckBox from '../FundingPay/CheckBox/CheckBox';
import KakaoPay from './KakaoPay/KakaoPay';

const FundingPay = () => {
    const navigate = useNavigate();

    const meta = document.createElement('meta');
    meta.name = 'viewport';
    meta.content = 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover';
    document.getElementsByTagName('head')[0].appendChild(meta);

    return (
        <MainContainer>
            <LeftContainer>
                <Logo>😉 Giftipie</Logo>
                <P pt="25px" fs="16px" fw="800" pb="5px">
                    기프티파이에서
                </P>
                <P fs="16px" fw="800" pb="5px">
                    정말 원하는 선물을
                </P>
                <P fs="16px" fw="800">
                    주고 받아요
                </P>
                <Button onClick={() => navigate('/')} mt="20px" w="180px" h="50px" fs="16px" color="white" bc="orange">
                    펀딩 시작하기
                </Button>
            </LeftContainer>

            <RightContainer>
                <Navbar>
                    <NavbarBtn onClick={() => navigate('/fundingdetail')} fs="15px" fw="800" pl="15px">
                        😉 펀딩 상세페이지로 이동
                    </NavbarBtn>
                </Navbar>

                <Body>
                    <FundingDiv>
                        <SponserMoney>
                            <SponsorImg src="/imgs/junjihyun.jpg" alt="logo" />
                            <P pt="10px" fs="16px" fw="800" pb="5px">
                                윤다인 님에게
                            </P>
                            <P fs="16px" fw="800" pb="5px">
                                5,000원
                            </P>
                            <P fs="16px" fw="800">
                                후원하기
                            </P>
                        </SponserMoney>
                        <P pt="20px" pb="20px" fs="16px" fw="900">
                            후원자
                        </P>

                        <SponserDiv>
                            <SponserComment mt="10px">
                                <P pl="10px" pb="5px" fs="13px" fw="800">
                                    이름
                                </P>
                                <InputTag type="text" placeholder="남길 이름을 입력해주세요" h="40px" />
                                <P pl="10px" fs="10px" fw="800">
                                    주최자에게 이름이 모두 공개되고, 후원자 목록에는 두번째 글자부터 *으로 표시됩니다.
                                    예) 김 * *
                                </P>
                            </SponserComment>
                        </SponserDiv>

                        <P pt="10px" pl="10px" pb="5px" fs="13px" fw="800">
                            메시지
                        </P>
                        <InputTag type="text" placeholder="남길 메시지를 입력해주세요" pb="50px" h="100px" />

                        <P pl="10px" fs="10px" fw="800">
                            현재는 테스트 기간으로, 실제 결제가 이루어지지 않습니다. 대신 1명이 참여할 때마다 개설자에게
                            1,000원이 적립됩니다.
                        </P>
                    </FundingDiv>

                    <CheckBox />

                    <TogetherDiv pt="10px" bc="orange">
                        <P pl="140px" fs="14px" fw="800">
                            <br />
                            지금 선물하면 3등이에요!
                            <br />
                        </P>
                    </TogetherDiv>

                    <KakaoPay />
                </Body>
            </RightContainer>
        </MainContainer>
    );
};

export default FundingPay;

import styled from "styled-components";

// 전체 컨테이너
export const MainContainer = styled.div`
  display: flex;
  justify-content: center; 
  max-width: 1200px; 
  min-height: 100vh;
  margin: 0 auto;
  flex-wrap: wrap;
`;

// 왼쪽 컨테이너
export const LeftContainer = styled.div`
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  width: 500px;
  height: 100vh;
  padding: 20px;
  border: 1px solid lightgray;
  border-radius: 8px;
  margin-right: 100px;

  @media (max-width: 1024px) {
    display: none;
  }
`;

export const Logo = styled.h1`
  font-size: 24px;
  font-weight: 800;
`;

export const P = styled.p`
  padding-top: ${(props) => props.pt};
  padding-bottom: ${(props) => props.pb};
  padding-left: ${(props) => props.pl};
  padding-right: ${(props) => props.pr};
  font-size: ${(props) => props.fs};
  font-weight: ${(props) => props.fw};
  color: ${(props) => props.color};
  align-items: center;
`;

export const Button = styled.button`
  justify-content: center;
  align-items: center;
  width: ${(props) => props.w};
  height: ${(props) => props.h};
  background-color: ${(props) => props.bc};
  border-radius: 7px;
  color: ${(props) => props.color};
  font-size: ${(props) => props.fs};
  font-weight: 600;
  margin-top: ${(props) => props.mt};
  margin-bottom: ${(props) => props.mb};
  padding-left: ${(props) => props.pl};
  padding-right: ${(props) => props.pr};
  &:hover {
    color: white;
    background-color: black;
    cursor: pointer;
  }
`;

// 오른쪽 컨테이너
export const RightContainer = styled.div`
  position: relative;
  width: 442px;
  border: 1px solid lightgray;
  height: 100vh;
  overflow-y: scroll;

  &::-webkit-scrollbar {
    display: none;
  }

  @media (max-width: 442px) {
    width: 100%;
  }
`;

// 네브바 영역
export const Navbar = styled.div`
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  align-items: center;
  height: 70px;
`;

export const NavbarBtn = styled.button`
  font-size: ${(props) => props.fs};
  font-weight: ${(props) => props.fw};
  padding-left: ${(props) => props.pl};
  padding-right: ${(props) => props.pr};
`;

// 바디 영역
export const Body = styled.div`
  font-size: 24px;
  font-weight: 800;
  height: 2100px;
`;

export const FundingDiv = styled.div`
  justify-content: center;
  width: 100%;
  max-width: 442px;
  height: auto;
  padding: 30px;
`;

export const SponserDiv = styled.div`
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 15px;
`;

export const SponserMoney = styled.div`
  margin-top: ${(props) => props.mt};
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
`;

export const SponserComment = styled.div`
  margin-top: ${(props) => props.mt};
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: start;
`;

export const SponsorImg = styled.img`
  width: 60px;
  height: 60px;
  border-radius: 100px;
  margin-top: 10px;
`;

export const InputTag = styled.input`
    width: 98%;
    height: ${(props) => props.h};
    background-color: #eae7de;
    border-radius: 4px;
    border: none;
    margin-left: 10px;
    margin-bottom: 10px;
    padding-left: 10px;
    padding-bottom: ${(props) => props.pb};
    font-weight: 500;
    font-size: 11px;
    justify-content: start;
    align-items: start;
`;

export const FundingNewline = styled.div`
  width: 100%;
  height: 12px;
`;

export const TogetherDiv = styled.div`
  margin-top: 30px;
  width: 442px;
  height: 45px;
  background-color: ${(props) => props.bc};
  color: ${(props) => props.color};
`;
import React, { useEffect } from 'react'; // React와 useEffect 훅을 React 라이브러리에서 불러옵니다.
import axios from 'axios'; // HTTP 요청을 보내기 위해 axios를 가져옵니다.
import {
    Button,
    KakaoPayLogo,
} from './KakaoPayStyles';

const KakaoPay = () => {
    // KakaoPay라는 함수형 컴포넌트를 정의합니다.
    const sendAmountToServer = async (amount) => {
        // 서버로 금액을 전송하는 비동기 함수를 정의합니다.
        const accessToken = localStorage.getItem('accessToken'); // localStorage에서 accessToken을 가져옵니다.
        try {
            const response = await axios.post(
                'http://localhost:8081/api/point',
                {
                    // 서버로 POST 요청을 보냅니다.
                    point: amount, // 요청 본문에 금액을 담습니다.
                },
                {
                    headers: {
                        Authorization: accessToken, // 요청 헤더에 accessToken을 포함시킵니다.
                    },
                }
            );

            if (response.status === 200) {
                // 요청이 성공했는지 확인합니다.
                alert('서버로 amount 전송 완료'); // 서버로의 전송이 성공한 경우 알림창을 띄웁니다.
            } else {
                alert('서버로 amount 전송 실패'); // 서버로의 전송이 실패한 경우 알림창을 띄웁니다.
            }
        } catch (error) {
            console.error('서버로 amount 전송 중 오류:', error); // 전송 중 오류가 발생한 경우 콘솔에 오류를 기록합니다.
            alert('서버로 amount 전송 중 오류 발생'); // 전송 중 오류가 발생한 경우 알림창을 띄웁니다.
        }
    };

    const requestPay = () => {
        // 결제 요청을 처리하는 함수를 정의합니다.
        const { IMP } = window; // window 객체에서 IMP 객체를 가져옵니다.

        IMP.init('imp82385247'); // IMP 객체를 초기화합니다.

        IMP.request_pay(
            {
                // IMP 객체를 사용하여 결제 요청을 보냅니다.
                pg: 'kakaopay.TC0ONETIME', // 결제 수단을 설정합니다.
                pay_method: 'card', // 결제 방법을 설정합니다.
                merchant_uid: new Date().getTime(), // 고유한 상점 ID를 생성합니다.
                name: 'Giftipie후원금', // 상품 이름을 설정합니다.
                amount: 10000, // 결제 금액을 설정합니다.
                buyer_email: 'test@naver.com', // 구매자 이메일을 설정합니다.
                buyer_name: '코드쿡', // 구매자 이름을 설정합니다.
                buyer_tel: '010-1234-5678', // 구매자 전화번호를 설정합니다.
                // buyer_addr: '서울특별시', // 구매자 주소를 설정합니다.
                // buyer_postcode: '123-456', // 구매자 우편번호를 설정합니다.
            },
            async (rsp) => {
                // 결제 요청 후 콜백 함수를 처리합니다.
                try {
                    const { data } = await axios.post('http://localhost:8081/verifyIamport/' + rsp.imp_uid); // 서버에서 결제 검증을 수행합니다.
                    if (rsp.paid_amount === data.response.amount) {
                        // 결제 금액이 서버 응답과 일치하는지 확인합니다.
                        alert('결제 성공'); // 결제가 성공한 경우 알림창을 띄웁니다.
                        sendAmountToServer(rsp.paid_amount); // 결제된 금액을 서버로 전송합니다.
                    } else {
                        alert('결제 실패'); // 결제가 실패한 경우 알림창을 띄웁니다.
                    }
                } catch (error) {
                    console.error('결제 검증 중 오류:', error); // 결제 검증 중 오류가 발생한 경우 콘솔에 오류를 기록합니다.
                    alert('결제 실패'); // 결제 검증 중 오류가 발생한 경우 알림창을 띄웁니다.
                }
            }
        );
    };

    useEffect(() => {
        // 외부 스크립트를 로드하기 위해 useEffect 훅을 사용합니다.
        const jquery = document.createElement('script'); // jQuery 스크립트 엘리먼트를 생성합니다.
        jquery.src = 'http://code.jquery.com/jquery-1.12.4.min.js'; // jQuery 스크립트의 소스를 설정합니다.
        const iamport = document.createElement('script'); // Iamport 스크립트 엘리먼트를 생성합니다.
        iamport.src = 'http://cdn.iamport.kr/js/iamport.payment-1.1.7.js'; // Iamport 스크립트의 소스를 설정합니다.
        document.head.appendChild(jquery); // jQuery 스크립트를 문서 헤드에 추가합니다.
        document.head.appendChild(iamport); // Iamport 스크립트를 문서 헤드에 추가합니다.
        return () => {
            // 컴포넌트가 언마운트될 때 추가한 스크립트를 제거하기 위한 클린업 함수를 반환합니다.
            document.head.removeChild(jquery); // 문서 헤드에서 jQuery 스크립트를 제거합니다.
            document.head.removeChild(iamport); // 문서 헤드에서 Iamport 스크립트를 제거합니다.
        };
    }, []); // useEffect가 초기 렌더링 후 한 번만 실행되도록 빈 의존성 배열을 전달합니다.

    return (
        <div>
            {/* <button onClick={requestPay}>결제하기</button> */}
            <Button onClick={requestPay}>
            <KakaoPayLogo src="/imgs/kakaopay.png" alt="image" />
            </Button>
        </div>
    );
};

export default KakaoPay; // Payment 컴포넌트를 내보냅니다.
import styled from "styled-components";

export const Button = styled.button`
  justify-content: center;
  align-items: center;
  width: 100%;
  height: 60px;
  background-color: #fae101;
  border-radius: 7px;
  font-size: 19px;
  font-weight: 600;
  margin-top: ${(props) => props.mt};
  margin-bottom: ${(props) => props.mb};
  padding-left: ${(props) => props.pl};
  padding-right: ${(props) => props.pr};
  &:hover {
    background-color: #fae102;
    cursor: pointer;
  }
`;

export const KakaoPayLogo = styled.img`
    height: 35px;
    margin-top: 5px;
`
import React, { useState, useEffect } from 'react';

import {
    CheckBoxDiv,
    Checkbox,
    FontSize5,
} from './CheckBoxStyles';

function CheckBox() {
    const [allCheck, setAllCheck] = useState(false);
    const [ageCheck, setAgeCheck] = useState(false);
    const [useCheck, setUseCheck] = useState(false);
    const [infoCheck, setInfoCheck] = useState(false);
    const [marketingCheck, setMarketingCheck] = useState(false);

    const allBtnEvent = () => {
        if (allCheck === false) {
            setAllCheck(true);
            setAgeCheck(true);
            setUseCheck(true);
            setInfoCheck(true);
            setMarketingCheck(true);
        } else {
            setAllCheck(false);
            setAgeCheck(false);
            setUseCheck(false);
            setInfoCheck(false);
            setMarketingCheck(false);
        }
    };

    const ageBtnEvent = () => {
        if (ageCheck === false) {
            setAgeCheck(true);
        } else {
            setAgeCheck(false);
        }
    };

    const useBtnEvent = () => {
        if (useCheck === false) {
            setUseCheck(true);
        } else {
            setUseCheck(false);
        }
    };

    const infoBtnEvent = () => {
        if (useCheck === false) {
            setInfoCheck(true);
        } else {
            setInfoCheck(false);
        }
    };

    const marketingBtnEvent = () => {
        if (marketingCheck === false) {
            setMarketingCheck(true);
        } else {
            setMarketingCheck(false);
        }
    };

    useEffect(() => {
        if (ageCheck === true && useCheck === true && infoCheck === true && marketingCheck === true) {
            setAllCheck(true);
        } else {
            setAllCheck(false);
        }
    }, [ageCheck, useCheck, infoCheck, marketingCheck]);

    return (
        <div>
            <CheckBoxDiv>
                <Checkbox type="checkbox" id="all-check" checked={allCheck} onChange={allBtnEvent} />
                <FontSize5>모두 동의합니다</FontSize5>
            </CheckBoxDiv>
            <CheckBoxDiv>
                <Checkbox type="checkbox" id="check1" checked={ageCheck} onChange={ageBtnEvent} />
                <FontSize5>만 14세 이상 가입 동의 (필수)</FontSize5>
            </CheckBoxDiv>
            <CheckBoxDiv>
                <Checkbox type="checkbox" id="check2" checked={useCheck} onChange={useBtnEvent} />
                <FontSize5>이용약관 동의 (필수)</FontSize5>
            </CheckBoxDiv>
            <CheckBoxDiv>
                <Checkbox type="checkbox" id="check3" checked={infoCheck} onChange={infoBtnEvent} />
                <FontSize5>개인정보 수집/이용 동의 (필수)</FontSize5>
            </CheckBoxDiv>
            <CheckBoxDiv>
                <Checkbox type="checkbox" id="check4" checked={marketingCheck} onChange={marketingBtnEvent}/>
                <FontSize5>마케팅 정보 수신 동의 (선택)</FontSize5>
            </CheckBoxDiv>
        </div>
    );
}

export default CheckBox;
import styled from "styled-components";

export const CheckBoxDiv = styled.div`
    display: flex;
    flex-direction: row;
    align-items: center;
    height: 21px;
`;
export const Checkbox = styled.input`
    margin-right: 10px;
    margin-left: 45px;
    width: 12px;
    height: 12px;
    accent-color: black;
`;
export const FontSize5 = styled.h5`
    font-size: 10px;
    font-weight: 500;
`;