새소식

FE/기능

React에서 Portone(포트원) 결제 연동하기(카카오페이)

  • -

# 배경

하루네컷 서비스에서 결제기능을 맡게 되었다.

결제 요청은 프론트에서 진행하고, 그것을 검증하는 단계를 백엔드에서 맡아주기로 했다. 우리가 진행한 방식은 아래와 같다

 

 

# 결제 연동 코드 작성 전 준비 단계

결제 연동 코드를 작성하기 위해, 알고 있어야 할 정보가 있다. 

1. 고객사 식별 코드(IMP)

2. PG 상점 아이디(MID)

테스트 용으로 채널 등록을 할 때, '일반 결제'를 선택했더니 제공되었다

 

 

 

# 포트원 라이브러리 추가

- 공식 문서에는 리액트 메뉴얼이 없어서, 리액트로 변경했다. 코드는 다른께서 블로그에 잘 정리해주셔서 활용했다

- useEffect 안에 스트립트를 넣었다. 

useEffect(() => {
  // 포트원 라이브러리 추가
  let script = document.querySelector(
    `script[src="https://cdn.iamport.kr/v1/iamport.js"]`
  );

  // 만약 스크립트가 존재하지 않으면
  if (!script) {
    // 새로운 스크립트 요소를 생성
    script = document.createElement("script");
    script.src = "https://cdn.iamport.kr/v1/iamport.js";
    script.async = true;
    document.body.appendChild(script); // 스크립트를 문서의 body에 추가
  }

  // 컴포넌트가 언마운트될 때 실행되는 함수 반환
  return () => {
    // 스크립트 요소가 존재하는지 확인 후 제거
    if (script && script.parentNode === document.body) {
      document.body.removeChild(script);
    }
  };
}, []);

HTML 문서에 <script> 요소가 중복해서 삽입되는 것을 원천적으로 막기위해,  HTML 문서에서 해당 <script> 요소가 이미 존재하는지 확인하는 예외처리가 들어갔다

https://www.daleseo.com/react-hooks-use-script/

 

React에서 <script> 태그로 자바스크립트 불러오기

Engineering Blog by Dale Seo

www.daleseo.com

 

# onclickPay() 함수로 결제 요청

onclickPay()함수를 만들어서, 결제 요청하는 기능을 담당하도록 했다.

const onclickPay = (pgValue, payMethod) => {
    window.IMP.init("imp11122174");
    const impUid = "imp11122174";

    let data;
    if (selectedPencil === 1) {
      data = {
        pg: pgValue,
        pay_method: payMethod,
        merchant_uid: generateMerchantUid(),
        name: "20연필(HARU4CUT)",
        amount: 3000,
        m_redirect_url: "",
      };
    } else if (selectedPencil === 2) {
      data = {
        pg: pgValue,
        pay_method: payMethod,
        merchant_uid: generateMerchantUid(),
        name: "50연필(HARU4CUT)",
        amount: 5000,
        m_redirect_url: "",
      };
    } else if (selectedPencil === 3) {
      data = {
        pg: pgValue,
        pay_method: payMethod,
        merchant_uid: generateMerchantUid(),
        name: "100연필(HARU4CUT)",
        amount: 10000,
        m_redirect_url: "",
      };
    }

    console.log("data:", data);

    window.IMP.request_pay(data, async (rsp) => {
      if (rsp.success) {
        const success = await verifyPayment(data.merchant_uid, impUid);
        if (success) {
          console.log("결제 성공");
        } else {
          console.log("결제 검증 실패");
        }
      } else {
        console.log("결제 실패");
      }
    });
  };

 

data에서 pg에 대해 설명하자면, pg에는 pg사 구분코드.{ PG 상점 아이디 }가 들어간다.- pg사 구분코드. PG 상점 아이디 예시 )

kakaopay.TC0ONETIME

- pg상점 아이디는 앞서, 사이트로부터 얻어왔었다 

 

  • 사용자가 선택한 옵션을 redux로 가져와서 selectedPencil 변수에 저장한 후, selectedPencil의 값에 따라 조건부로 data설정하였다.
  • merchantUId는 겹치지 않도록 랜덤값으로 달라는 요청에 따라, generatemerchantUId함수를 통해, 랜덤값으로 넘겨주었다.

 

 

# verifyPayment() 함수로 결제 시 검증하기

  const verifyPayment = async (merchantUid, impUid) => {
    try {
      const response = await axios.post(
        `/verify/${merchantUid}/${userId}/${impUid}` 
      );
      return response.data.success; // 성공 여부를 반환
    } catch (error) {
      console.error("결제 검증 요청 에러:", error);
      return false; 
    }
  };

# 버튼 클릭 시 결제창 띄우기

<button onClick={() => onclickPay("kakaopay.TC0ONETIME", "kakaopay")}>

 

# 전체 코드

import React, { useEffect } from "react";
import { useSelector } from "react-redux";
import axios from "axios";

const PaymentPage = () => {
  const userId = useSelector((state) => state.userId);
  const selectedPencil = useSelector((state) => state.selectedBox);

  const generateMerchantUid = () => {
    return "HARU4CUT-" + Math.random().toString(36).substr(2, 9);
  };

  useEffect(() => {
    // 포트원 라이브러리 추가
    let script = document.querySelector(
      `script[src="https://cdn.iamport.kr/v1/iamport.js"]`
    );

    if (!script) {
      script = document.createElement("script");
      script.src = "https://cdn.iamport.kr/v1/iamport.js";
      script.async = true;
      document.body.appendChild(script);
    }
    return () => {
      // 스크립트 요소가 존재하는지 확인 후 제거
      if (script && script.parentNode === document.body) {
        document.body.removeChild(script);
      }
    };
  }, []);

  const onclickPay = (pgValue, payMethod) => {
    window.IMP.init("imp11122174");
    const impUid = "imp11122174";

    let data;
    if (selectedPencil === 1) {
      data = {
        pg: pgValue,
        pay_method: payMethod,
        merchant_uid: generateMerchantUid(),
        name: "20연필(HARU4CUT)",
        amount: 3000,
        m_redirect_url: "",
      };
    } else if (selectedPencil === 2) {
      data = {
        pg: pgValue,
        pay_method: payMethod,
        merchant_uid: generateMerchantUid(),
        name: "50연필(HARU4CUT)",
        amount: 5000,
        m_redirect_url: "",
      };
    } else if (selectedPencil === 3) {
      data = {
        pg: pgValue,
        pay_method: payMethod,
        merchant_uid: generateMerchantUid(),
        name: "100연필(HARU4CUT)",
        amount: 10000,
        m_redirect_url: "",
      };
    }

    console.log("data:", data);

    window.IMP.request_pay(data, async (rsp) => {
      if (rsp.success) {
        const success = await verifyPayment(data.merchant_uid, impUid);
        if (success) {
          console.log("결제 성공");
        } else {
          console.log("결제 검증 실패");
        }
      } else {
        console.log("결제 실패");
      }
    });
  };

  const verifyPayment = async (merchantUid, impUid) => {
    try {
      const response = await axios.post(
        `/verify/${merchantUid}/${userId}/${impUid}`
      );
      return response.data.success;
    } catch (error) {
      console.error("결제 검증 요청 에러:", error);
      return false;
    }
  };

  return (
    <>
      <button onClick={() => onclickPay("kakaopay.TC0ONETIME", "kakaopay")}>
        카카오페이
      </button>
    </>
  );
};

export default PaymentPage;

# 참고

https://pink1016.tistory.com/264

 

[React] PortOne(포트원) 이용해서 결제 연동하기

# 배경 회사에서 개발자끼리 스터디를 하기로 했다. 경매 서비스를 만들기로 했다. 거기서 결제하는 부분을 맡게 되었다. # 참조 https://portone.gitbook.io/docs/ready 결제 연동 준비하기 - PortOne Docs 포트

pink1016.tistory.com

 

지금은 서버 준비가 안되어서, 실 테스트는 내일 해보고 다시 작성하기

'FE > 기능' 카테고리의 다른 글

React 카카오 소셜 로그인 구현(REST API)  (0) 2024.05.30
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.