[Spring] 포트원(아임포트)결제api 사용하기 #ajax

호연지기·2023년 8월 11일
1
post-thumbnail

✏️ 결제api 프로세스

  1. 사용자가 상품을 선택하고 결제하기 버튼을 누른다.
  2. 결제창 팝업창 작동
  3. 결제 처리
  4. 데이터베이스에 데이터 저장
  5. 사용자는 결제완료 페이지로 이동

✏️ 사전준비

1. 포트원(아임포트) 회원가입

결제api 활용 포스팅을 찾아보면 대부분 아임포트 결제api를 사용하는데 아임포트가 포트원으로 바뀌면서 사용방법도 조금 달라졌다.
가이드를 보면서 진행하면 훨 수월하니 링크 첨부!

◼︎ 포트원 결제연동 가이드
https://developers.portone.io/docs/ko/ready/readme

◼︎ 아임포트 결제연동 가이드
https://github.com/iamport/iamport-manual/tree/master

◼︎ 포트원 로그인 페이지
https://admin.portone.io/auth/signin
회원가입은 위 링크에서 메일주소로 처리할 수 있다.

2. 결제 api 발급받기

사이드바의 결제 연동 메뉴에서 식별코드, REST API KEY를 확인할 수 있고 결제대행사를 추가할 수 있다.

카카오페이랑 KG이니시스를 테스트모드로 추가했다.

✏️ 스프링에서 데이터 처리하기

💻 html 작성 (ajax로 넘겨주기)

<script src="https://cdn.iamport.kr/v1/iamport.js"></script>

html 상단에 iamport 스크립트 추가

<div class="kg_pay_btn">
  <button type="button" th:onclick="kg_request_pay()">결제하기</button>
</div>
<div class="ka_pay_btn">
  <button type="button" th:onclick="ka_request_pay()">카카오페이</button>
</div>

kg 이니시스 버튼과 카카오페이 버튼 각각 하나씩 생성 후
클릭 이벤트를 걸어주었다.

<script th:inline="javascript">

  function kg_request_pay() {
    //전달할 데이터
    var selectedDate = document.getElementById("datepicker").value;
    var selectedGoodsName = document.querySelector(".kg_pay_btn").getAttribute("data-name");
    var selectedGoodsPrice = document.querySelector(".kg_pay_btn").getAttribute("data-price");
    var selectedGoodsNum = document.querySelector(".kg_pay_btn").getAttribute("data-goodsnum");

    // datepicker가 선택되지 않은 경우 알림창 띄움
    if (selectedDate == "") {
      alert("운동 시작일을 선택해 주세요.");
      return;
    }

    //상품이 선택되지 않은 경우 알림창 띄움
    if(selectedGoodsName == null){
      alert("상품을 먼저 선택해 주세요.");
      return;
    }

    //kg이니시스 결제 API
    var IMP = window.IMP; // 생략가능
    IMP.init('###가맹점 식별코드###');  // 가맹점 식별코드

    // IMP.request_pay(param, callback) 결제창 호출
    IMP.request_pay({
      pg: "html5_inicis",
      pay_method: "card",
      merchant_uid: "gpay_" + new Date().getTime(),   // 주문번호
      name: [[${gym.gname}]] +" "+ selectedGoodsName,
      amount: selectedGoodsPrice,                         // 숫자 타입
      buyer_email: [[${member.mmail}]],
      buyer_name: [[${member.mname}]],
      buyer_tel: [[${member.mphone}]]
    }, function (rsp) { // callback
      console.log(rsp);
      if ( rsp.success ) { //결제 성공시
        var msg = '결제가 완료되었습니다.';
        var result = {
          "mpaynum" : rsp.merchant_uid, //결제번호
          "membernum" :[[${member.membernum}]], //회원번호
          "mpaymethod":rsp.pay_method, //결제수단
          "mpayproduct":rsp.name, //헬스장 이름 + 상품이름
          "mpayprice":rsp.paid_amount, // 결제금액
          "mpaydate" : new Date().toISOString().slice(0, 10), //결제일
          "mpaygym" : [[${gym.gname}]], //헬스장 이름
          "mpayperiod" : selectedDate, //상품이용기간
          "mpaytime" : "",
          "trainername":"",
          "ggoodsnum": selectedGoodsNum, //상품번호
          "tgoodsint" : null,
          "gymnum" : [[${gym.gymnum}]] //헬스장 고유번호
        }
        console.log(result);

        $.ajax({
          url:'insertMPay',
          type:'POST',
          contentType: 'application/json',
          data:JSON.stringify(result),
          success: function (res) {
            console.log(res);
            location.href=res;
          },
          error: function (err) {
            console.log(err);
          }
        }); //ajax
      } else {
          var msg = '결제 실패';
          msg += '\n에러내용 : ' + rsp.error_msg;
        }
      alert(msg);
    });
  }

  // 상품 선택 이벤트
  function toggleOn(element) {
    var ticketDivs = document.querySelectorAll('.ticket_list .on');
    for (var i = 0; i < ticketDivs.length; i++) {
      ticketDivs[i].classList.remove('on');
    }
    element.classList.add('on');

    //결제 데이터 전달
    var selectedPrice = element.querySelector("p").innerText;
    var priceElement = document.getElementById("selectedPrice").querySelector("span");
    priceElement.innerText = selectedPrice;

    var selectedProduct = element.querySelector("h5").innerText;
    var productElement = document.getElementById("selectedProduct").querySelector("span");
    productElement.innerText = selectedProduct;

    // 선택된 상품 정보 가져오기(상품명, 금액)
    var selectedGoodsName = element.getAttribute("data-goodsname");
    var selectedGoodsPrice = element.getAttribute("data-goodsprice");
    var selectedGoodsNum = element.getAttribute("data-goodsnum");

    //kg 이니시스
    var nameElement = document.querySelector(".kg_pay_btn");
    nameElement.setAttribute("data-name", selectedGoodsName);
    nameElement.setAttribute("data-price", selectedGoodsPrice);
    nameElement.setAttribute("data-goodsnum", selectedGoodsNum);
  }
</script>

결제 성공 여부에 따라 사용자에게 결제 성공/실패로 메세지를 띄우고, 결제 정보는 ajax를 활용하여 컨트롤러에 전달한다.
너무 길어져서 카카오페이는 결제 넘기는 건 생략했는데 똑같은 방식으로 데이터를 넘기면 된다.


헬스장 상품 클릭(회원권 or 일일권) ► 운동 시작날짜 선택 ► 결제하기 or 카카오페이 선택 ► 결제

kg 이니시스 결제창

카카오페이 결제창

💻 controller

public class MPayController {
    @Autowired
    private MPayService mPServ;

    private ModelAndView mv;

    @PostMapping("insertMPay")
    @ResponseBody
    public String insertMPay(@RequestBody MPayDto mpay, HttpSession session, RedirectAttributes rttr){
        log.info("insertMPay()");

        String view = mPServ.insertMPay(mpay, session, rttr);
        return view;
    }

        @GetMapping("payment")
    public ModelAndView paymentContents(String mpaynum, HttpSession session){
        log.info("paymentContents()");

        mv = mPServ.paymentContents(mpaynum, session);
        return mv;
    }
}

결제정보를 데이터베이스에 저장하기 위해 서비스로 mpy 정보와 session, rttr을 넘긴다.
결제가 완료되면 결제완료 페이지로 넘어가기 때문에 payment 페이지를 생성하여 결제완료 데이터를 갖고 넘어가도록 처리하였다.

💻 service

//결제정보 데이터베이스로 넘기기
public String insertMPay(MPayDto mpay, HttpSession session, RedirectAttributes rttr) {
    log.info("insertMPay()");
    String view = null;
    String msg = null;

    try {
        mPDao.insertMPay(mpay);

        view = "payment?mpaynum=" + mpay.getMpaynum();
        log.info(view);
    } catch (Exception e){
        e.printStackTrace();
    }
    return view;

}

Dao로 mpay 데이터를 넘겨주고, 결제완료 페이지에 결제 정보가 노출되도록 view를 설정한 뒤 return.

//결제완료 페이지 컨트롤
public ModelAndView paymentContents(String mpaynum, HttpSession session) {
  log.info("paymentContents()");
  mv = new ModelAndView();

  //주문정보 가져오기
  MPayDto mpay = mPDao.selectPayment(mpaynum);
  mv.addObject("mpay", mpay);
  int gymnum = mpay.getGymnum();
  int tgoodsint = mpay.getTgoodsint();

  //헬스장 정보 가져오기
  GymDto gym = GGDao.selectGym(gymnum);
  mv.addObject("gym", gym);
  //view 설정
  mv.setViewName("payment");

  return mv;
}

결제완료 페이지에서는 주문번호에 맞는 결제정보와 헬스장 정보를 가져오고 payment 페이지에 나타낸다.

💻 dao

@Mapper
public interface MPayDao {
    //헬스장 결제 정보를 저장하는 메소드
    void insertMPay(MPayDto mpay);
    
    //사용자의 결제 정보를 가져오는 메소드
    MPayDto selectPayment(String mpaynum);
}

💻 dao.xml

//데이터베이스에 결제정보 넣기
<insert id="insertMPay" parameterType="mPayDto">
    insert into mpay
    values (#{mpaynum},#{membernum}, #{mpaymethod}, #{mpayproduct}, #{mpayprice}, #{mpaydate},
            #{mpaygym}, #{mpayperiod}, #{mpaytime},#{trainername}, #{ggoodsnum}, null, #{gymnum}, 0)
</insert>

//데이터베이스에서 해당 결제정보 가져오기
<select id="selectPayment" resultType="MPayDto" parameterType="String">
    select * from mpay where mpaynum=#{mpaynum}
</select>

✏️ 결제 완료

결제가 정상적으로 처리되면 메세지와 함께 결제완료 페이지로 이동한다.

결제완료 페이지에는 헬스장의 기본 정보와 위치, 결제 정보 등이 노출된다.

마이페이지 결제내역에서도 결제내역을 확인할 수 있다.

📅 DATE

2023.08.11 작성

profile
사람의 마음에 차 있는 너르고 크고 올바른 기운

2개의 댓글

comment-user-thumbnail
2023년 8월 11일

글 재미있게 봤습니다.

답글 달기
comment-user-thumbnail
2023년 12월 1일

안녕하세요 혹시 해당 결제 파트를 구현하다가 참고를 하고싶은데 코드 전체 공유 가능할까요?! ㅠㅠ

답글 달기