포트원(아임포트) 알아보기

SU-DAA·2024년 2월 2일
1
post-thumbnail

💡 PG사란 ?
: Payment Gateway 의 줄임말
구매자와 판매자 사이에서의 이뤄지는 결제를 안전하게 할 수 있도록 대행해주는 역할을 담당
대표적인 PG사로는 KG 이니시스, NHN, KCP, LGU+ 등이 있으며,
모바일 환경으로는 KG 모빌리언스, 다날, 카카오Pay 등이 있다 .

포트원(아임포트) 연동 준비

1. 포트원 회원가입 후 테스트 결제대행사 추가

포트원: https://admin.portone.io/
위 주소로 들어가서 회원가입 후 '결제연동' 탭으로 이동후 원하는 결제대행사 테스트로 추가한다
나는 KG이니시스를 선택하였다

2. 결제 연동하기

사실 프론트의 결제 연동하는 코드는 위에 올렸던 메뉴바 가장 아래에 위치해있는 '콘솔가이드'에 자세히 나와있다
https://developers.portone.io/docs/ko/auth/guide/readme?v=v1

아래는 인증결제가 어떻게 이루어지는지 잘 설명되어있는 그림이다

나는 백엔드 쪽을 개발하는거라 프론트 코드는 위 사이트에 들어가서 참고하면 될것 같다.
(우리 프론트 팀원도 해당 문서에 나와있는 코드를 거의 그대로 썻다고 한다!)

3. 포트원 관련 설정

프로젝트를 시작하기 전에 실제 pg창이 뜨는지 궁금해서 간단하게 적어본 html은 아래와 같았다.

index.html

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="utf-8">
</head>
<body> <!-- jQuery -->
<script type="text/javascript" src="https://code.jquery.com/jquery-1.12.4.min.js"></script> <!-- iamport.payment.js -->
<script type="text/javascript" src="https://cdn.iamport.kr/js/iamport.payment-1.2.0.js"></script>
<div><h2>IAMPORT 결제 데모</h2>
    <li>
        <button id="iamportPayment" type="button">결제테스트</button>
    </li>
</div>
<script>
    //문서가 준비되면 제일 먼저 실행
    $(document).ready(function () {
        $("#iamportPayment").click(function () {
            proceedPay(); //버튼 클릭하면 호출
        });
    })

    function proceedPay() {
        $.ajax({
            url: '/payment/proceed',
            type: 'POST',
            async: true,
            dataType: "Json",
            data:
                $('#orderForm').serialize(),
            success: function (data) {
                if (data.cnt > 0) {
                    requestPay(data)
                } else {
                    alert(data.msg)
                }
            },
            error: function (e) {
                alert("에러")
            }
        });

    }

    function requestPay(data) {
        IMP.init("가맹점 코드"); // 예: imp00000000
        //IMP.request_pay(param, callback) 결제창 호출

        IMP.request_pay({ // param
            pg: "html5_inicis.INIBillTst", //결제대행사 설정에 따라 다르며 공식문서 참고
            pay_method: "card", //결제방법 설정에 따라 다르며 공식문서 참고
            merchant_uid: data.no, //주문(db에서 불러옴) 고유번호
            name: data.products,
            amount: data.price,
            buyer_email: "",
            buyer_name: data.name,
            //buyer_tel: "010-4242-4242",
            buyer_addr: data.addr,
            //buyer_postcode: "01181"
        }, function (rsp) { // callback
            if (rsp.success) {
                // 결제 성공 시: 결제 승인 또는 가상계좌 발급에 성공한 경우
                // jQuery로 HTTP 요청
                jQuery.ajax({
                    url: "/payment/verify/" + rsp.imp_uid,
                    method: "POST",
                }).done(function (data) {
                    // 위의 rsp.paid_amount 와 data.response.amount를 비교한후 로직 실행 (iamport 서버검증)
                    if (rsp.paid_amount == data.response.amount) {
                        succeedPay(rsp.imp_uid, rsp.merchant_uid);
                    } else {
                        alert("결제 검증 실패");
                    }
                })
            } else {
                var msg = '결제에 실패하였습니다.';
                msg += '에러내용 : ' + rsp.error_msg;
                alert(msg);
            }
        });
    }
</script>
</body>
</html>

build.gradle에 포트원 관련 설정을 추가하였다.

repositories {
    mavenCentral()

    //iamport
    maven {
        url 'https://jitpack.io'
    }
}
dependencies {
    //iamport
    implementation 'com.github.iamport:iamport-rest-client-java:0.2.23'
}

추가로 아래 결제 검증을 할 때 필요한 설정 파일들을 만들어주었다. 이때 impKey, impSecretKey는 유출되면 안된다고 하여 properties 파일에 숨겨서 사용하였다.

IamportApiProperty.java

@Getter
@Configuration
@PropertySource("classpath:/secret.properties")
public class IamportApiProperty {
    @Value("${imp_key}")
    private String impKey;
    @Value("${imp_secret}")
    private String impSecret;
}

IamportConfig.java

@Configuration
@RequiredArgsConstructor
public class IamportConfig {
    private final IamportApiProperty iamportApiProperty;

    @Bean
    public IamportClient iamportClient() {
        return new IamportClient(iamportApiProperty.getImpKey(), iamportApiProperty.getImpSecret());
    }
}

4. 결제 검증


문서를 참고하면서 개발하다보면 결제정보를 검증하는 과정이 꼭 필요하다고 나와있었고 그 이유도 잘 나와있어서 우리도 결제정보를 검증하는 과정을 추가하게 되었다.

위 화면에서 보면 백엔드에서 검증은 총 2번 1. 사전검증, 2. 사후검증을 진행하도록 되어있다.

  1. 사전검증
    결제금액 사전등록 API를 요청하여 포트원 측에 주문아이디와 가격을 등록한다

    https://developers.portone.io/api/rest-v1/payment.validation?v=v1#post%20%2Fpayments%2Fprepare

    IamportRepository.java

    public void prepare(Long orderId, BigDecimal price) {
            try {
                iamportClient.postPrepare(new PrepareData(String.valueOf(orderId), price));
            } catch (Exception e) {
                throw new CustomException(ErrorCode.IAMPORT_ERROR);
            }
        }
  2. 사후 검증
    1. 사전 검증 이후에 프론트 측에서 결제를 끝내고 나면 받게되는 포트원 결제고유번호(imp_uid), 가맹점 주문번호(merchant_uid)를 프론트엔드로부터 수신
    2. 결제 상세내역 조회를 위해 포트원 결제 단건 조회 API 요청
    3. 응답받은 내용을 바탕으로 실 결제 금액과 결제요청금액(가맹점 자체 데이터베이스)을 비교
    사후 검증은 결제가 이루어지고 난 다음에 우리의 데이터베이스에 저장된것과 비교하여 결제가 잘 이루어 진게 맞는지 검증하는 과정이라 위 절차를 순서대로 진행하면 된다.

    나는 PaymentRequest의 결제고유번호(imp_uid), 가맹점 주문번호(merchant_uid)를 받고, 포트원 결제 단건 조회 API 요청하여 주문테이블에 저장되어있는 정보와 비교하여 정상적인 결제가 이루어졌다고 생각하면 결제테이블에 단건 조회 결과 = 결제 정보 를 저장하고 있다.(우리의 서비스가 복잡하여 실제로는 상태값들을 조회하는 과정이 많이 포함되어 있어서 실제 저장하고 있는 코드는 다른 글에서 다룰 예정이다!)

    IamportRepository.java

    public Optional<Payment> findPaymentByImpUid(String impUid) {
            try {
                IamportResponse<com.siot.IamportRestClient.response.Payment> response =
                        iamportClient.paymentByImpUid(impUid);
                return Optional.ofNullable(paymentMapper.mapFrom(response.getResponse()));
            } catch (Exception e) {
                throw new CustomException(ErrorCode.IAMPORT_ERROR);
            }
        }

이렇게 하면 간단한 결제 구현은 할 수 있다 😉

profile
아직은 감자

0개의 댓글

관련 채용 정보