[Spring] Iamport 결제기능 구현

YangCheolJin·2022년 10월 5일
1

목차

  1. 세팅하기(gradle 기준)
  2. 결제기능 구현하기(DB 에 값을 넣는 것까지)
  3. 결제 취소하기

1. 세팅하기

build.gradle 에 다음과 같은 코드를 추가해주면 된다.

implementation 'com.github.iamport:iamport-rest-client-java:0.2.21'

repositories {
maven { url 'https://jitpack.io' }
}

이후 설치 작업이 끝나면 Iamport 의 라이브러리를 사용할 수 있게 된다.

2. 결제기능 구현하기

본인은 세션을 이용하였다. 그리고 결제를 위한 Contoller 인 VerifyContoller 를 따로 생성하여 프로젝트를 진행했었다.
일단 가장 처음으로 VerifyController 에 생성자를 통해 Iamport 의 Rest api key 값등을 넣어주어야 한다.
기존 세션이 존재하지 않아 오류가 발생한다면 paymentByImpUid 메소드 로직에서 세션 생성하는 부분의 false 를 true 로 바꿔주거나 지워버리면 된다.

코드는 다음과 같고 주석을 통해 설명을 해놓았다.

package challenges.challenges.controller.payment;

import challenges.challenges.controller.member.SessionConst;
import challenges.challenges.controller.participant_challenge.PaymentReqDTO;
import challenges.challenges.domain.Challenge;
import challenges.challenges.domain.Member;
import challenges.challenges.service.challenge.ChallengeService;
import challenges.challenges.service.participant_challenge.ParticipantService;
import com.siot.IamportRestClient.IamportClient;
import com.siot.IamportRestClient.exception.IamportResponseException;
import com.siot.IamportRestClient.request.CancelData;
import com.siot.IamportRestClient.response.IamportResponse;
import com.siot.IamportRestClient.response.Payment;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.HashMap;

@Slf4j
@RequiredArgsConstructor
@RestController
public class VerifyController {

    private final IamportClient iamportClient;
    private final ParticipantService participantService;
    private final ChallengeService challengeService;



    //생성자를 통해 REST API 와 REST API secret 입력
    @Autowired
    public VerifyController(ParticipantService participantService, ChallengeService challengeService) {
        this.iamportClient = new IamportClient("...", "...");
        this.challengeService = challengeService;
        this.participantService = participantService;
    }


    //iamport를 이용하여 결제하기를 완료하였을때
    @PostMapping("/verifyIamport/{imp_uid}")
    public IamportResponse<Payment> paymentByImpUid(@PathVariable String imp_uid, HttpServletRequest request) throws IamportResponseException, IOException {
        log.info("paymentByImpUid 진입");
        IamportResponse<Payment> paymentIamportResponse = iamportClient.paymentByImpUid(imp_uid);
        Payment payment = paymentIamportResponse.getResponse();
        HttpSession session = request.getSession(false); //로그인이 된 사용자가 세션을 사용하고 있으므로 false 세팅을 해준것임
        session.setAttribute("payment",payment);
        session.setMaxInactiveInterval(60); //60초동안 세션을 유지하고 있겠다는 뜻임
        return paymentIamportResponse;
    }


    //DB에 값을 넣기 위한 작업
    @PostMapping("/challenge/{id}/payment")
    public ResponseEntity<?> savePayment(@PathVariable Long id, HttpServletRequest request, @RequestBody PaymentReqDTO paymentReqDTO) throws IamportResponseException, IOException {

        HashMap<String, String> response = new HashMap<>();

        HttpSession session = request.getSession(false);

        CancelData cancelData = new CancelData(paymentReqDTO.getImp_uid(), true);

        //-----------------로그인 처리 여부와 관련된 메소드이므로 결제와는 무관한 부분
        if(session == null) {
            iamportClient.cancelPaymentByImpUid(cancelData);
            response.put("response","로그인을 해야합니다.");
            return ResponseEntity.ok(response);
        }

        Member loginMember = (Member) session.getAttribute(SessionConst.LOGIN_MEMBER);

        if(loginMember == null) {
            iamportClient.cancelPaymentByImpUid(cancelData);
            response.put("response","로그인을 다시 해야합니다");
            return ResponseEntity.ok(response);
        }
        //---------------------------------------------------------------------

        //챌린지 찾아오기
        Challenge findChallenge = challengeService.findOne(id);

        //verifyIamport에서 세션을 만들어서 여기서 검증한 후 없애줘야함
        //여긴 결제승인을 제대로 수행된 것이 아닐때 작동한다.
        Payment payment = (Payment) session.getAttribute("payment");
        if(payment == null) {
            response.put("response","잘못된 접근입니다.");
            return ResponseEntity.ok(response);
        }

        //성공적으로 작동하면 try 안에 각 프로젝트의 payment table 에 알맞게 값을 넣어주는 로직을 작성하면된다.
        try {
            participantService.savePayment(paymentReqDTO.getImp_uid() ,paymentReqDTO.getP_price(), loginMember, findChallenge);
            response.put("response","1");
            return ResponseEntity.ok(response);
        }
        //Iamport 의 경우 오류가 발생해도 결제가 이루어지므로(돈이 빠져나간다는 소리) cancelPaymentByImpUid 함수를 이용해 꼭 환불이 될 수 있도록 해야한다.
        //cancelData 는 위에서 imp_uid 를 이용하여 불러왔으므로 그냥 함수의 매개변수값으로 바로 넣어주면 된다.
        catch (Exception e) {
            iamportClient.cancelPaymentByImpUid(cancelData);
            response.put("response","잘못된 접근입니다");
            return ResponseEntity.ok(response);
        }

    }


}

3. 결제 취소하기

이 부분이 가장 중요하다고 생각한다. 구글링을 하여도 잘 찾아볼 수 없었던 내용이기 때문이다. 본인 프로젝트에서는 챌린지에 기부를 하고 기부를하는 방식인데 기부 챌린지를 삭제하면 그동안 기부했던 사람들의 결제내역을 바탕으로 돈을 냈던 사람들에게 환불을 해주는 로직을 작성하였다.
쉽게 설명하면 결제 취소의 작동방식은 다음과 같다. 만약 상품이라고 생각하고 상품결제 내역이 있다고 보자. 그렇다면 상품을 결제한 사람을 통해 payment table 에서 값을 가져오고 그 결과 값에 저장된 imp_uid 값을 통해 취소를 해주면 되는 것이다.

중요! 이 부분은 다른 컨트롤러에서 작성한 것입니다. 따라서 여기 Controller 에서도 생성자를 통해 Rest api 값을 넣어주어야 합니다.

/**
     * 세션에 있는 값을 조회하고 세션에 있는 로그인 멤버와 챌린지를 생성한 멤버의 정보가 같다면 삭제를 해주고 나머지는 실패이다.
     */
    @PostMapping("/challenge/{id}/delete")
    public ResponseEntity<?> deleteChallenge(@PathVariable Long id, HttpServletRequest request) throws IamportResponseException, IOException {

        HashMap<String, String> response = new HashMap<>();

        HttpSession session = request.getSession(false);

        if(session == null) {
            response.put("response","로그인을 해야합니다.");
            return ResponseEntity.ok(response);
        }

        Member loginMember = (Member) session.getAttribute(SessionConst.LOGIN_MEMBER);
        if(loginMember == null) {
            response.put("response","로그인을 다시 해주세요");
            return ResponseEntity.ok(response);
        }

        Challenge challenge = challengeService.findOne(id);
        //log.info("challenge loginid ={}",challenge.getMember().getM_loginId());
        //log.info("loginMember loginId={}",loginMember.getM_loginId());
        if(!challenge.getMember().getM_loginId().equals(loginMember.getM_loginId())) {
            response.put("response","해당 챌린지를 삭제할 수 있는 권한이 없습니다.");
            return ResponseEntity.ok(response);
        }

        
		//여기서부터는 결제 취소로직입니다. 
        //일단 환불절차를 다 진행을 해주어야 한다.
        //삭제할 챌린지를 가지고 있는 파티시팬트 챌린지를 다 가져오고 파티시팬트의 페이먼트를 가지고 있는 페이먼트 정보를 다 가져온다.
        //그리고 삭제를 해준다.
        List<ParticipantChallenge> participantChallengeList = participantService.findParticipantListByChallenge(challenge);
        for (ParticipantChallenge participantChallenge : participantChallengeList) {
            //파티시팬트를 갖고 있는 페이먼트의 정보 모두 가져오기
            List<Payment> paymentList = participantService.findPaymentList(participantChallenge);
            for (Payment payment : paymentList) {
                //캔슬 데이터 생성
                CancelData cancelData = new CancelData(payment.getImp_uid(),true);
                iamportClient.cancelPaymentByImpUid(cancelData);
                //페이먼트 삭제해주기
                participantService.deletePayment(payment);
            }
        }


        challengeService.deleteChallenge(challenge);
        response.put("response","success");
        return ResponseEntity.ok(response);
    }

결론

개인적인 프로젝트를 기준으로 코드를 작성한 것이라 보기에 좋지 않을 수 있습니다.
주석을 통해 최대한 알아볼 수 있도록 작성을 하였으니 모르는것이 있다면 개인적으로 메일 보내주세요!

profile
KB국민은행

0개의 댓글