728x90
반응형

빌링키 발급에 성공했을 경우 아임포트가 보안상의 이유로 빌링키에 직접 접근할 수 없기 때문에 customer_uid를 이용해

   ( https://api.iamport.kr/subscribe/payments/again ) REST API 를 호출해 빌링키로 결제를 요청하도록 한다.

 

1. 빌링키 발급이 성공했을 경우에 axios로 백엔드에 직접 만든 라우터로 경로를 설정해준다.

(구독 신청이 성공할 경우 로그아웃해서 쿠키 정보를 갱신해준다.)

참고로 빌링키와 결제요청은 merchant_uid를 다르게 해야함

    const subscribeItemClickHandler = (e: any) => {
        var IMP = window.IMP;
        IMP.init(process.env.NEXT_PUBLIC_IMP_CODE); // iamport 가맹점 식별코드
        IMP.request_pay({
            pg: 'kcp_billing',
            pay_method: 'card', // 'card'만 지원됩니다.
            merchant_uid: "order_monthly_0001", // 상점에서 관리하는 주문 번호 (날짜를 붙이면 중복되지않아 좋습니다.)
            name: '최초인증결제',
            amount: 500, // 결제창에 표시될 금액. 실제 승인이 이뤄지지는 않습니다. (PC에서는 가격이 표시되지 않음)
            customer_uid: 'your-customer-unique-id', // 필수 입력.
            buyer_email: 'iamport@siot.do',
            buyer_name: '아임포트',
            buyer_tel: '02-1234-1234',
            m_redirect_url: '{모바일에서 결제 완료 후 리디렉션 될 URL}' // 예: https://www.my-service.com/payments/complete/mobile
        }, function (rsp: any) {
            if (rsp.success) {
                axios.post('/api/subscribe/payments',{
                    muser_id: loginData.userId, // 로그인 아이디
                    radioItem: radioItem, // 구독 개월 수 라디오 버튼
                    merchant_uid: "위에 적은 merchant_uid",
                    customer_uid : "위에 적은 customer_uid",
                    name: "위에 적은 name",
                    amount: "위에 적은 amount"
                }).then(({ data }) => {
                    if (data.success) {
                        // 성공할 경우
                        axios.get('/api/auth/logout')
                            .then(({ data }) => {
                                if (data.success) {
                                    alert('구독신청이 완료되었습니다. 다시 로그인해주세요.')
                                    router.push('/');
                                }
                            })
                    }
                    else {
                        // 실패할 경우
                        alert('내용을 다시확인해주세요.')
                    }
                })
            } else {
                alert('내용을 다시확인해주세요.')
                console.log(rsp.error_msg);
            }
        });
    }

 

2. 백엔드에 클라이언트가 요청할 라우터를 만들어준다. 인증 토큰을 받고 결제 요청을 해야한다.

router.post("/payments", async function (req, res, next) {
    const body = req.body;
    console.log("body : ", body)
    try {
        // 인증 토큰 발급 받기
        const getToken = await axios({
            url: "https://api.iamport.kr/users/getToken",
            method: "post", // POST method
            headers: { "Content-Type": "application/json" }, // "Content-Type": "application/json"
            data: {
                imp_key: process.env.IMP_API_KEY, // REST API 키
                imp_secret: process.env.IMP_API_SECRET_KEY // REST API Secret
            }
        });
        const { access_token } = getToken.data.response; // 인증 토큰

        // 결제(재결제) 요청
        const paymentResult = await axios({
            url: 'https://api.iamport.kr/subscribe/payments/again',
            method: "post",
            headers: { "Authorization": access_token }, // 인증 토큰을 Authorization header에 추가
            data: {
                customer_uid: body.customer_uid,
                merchant_uid: body.merchant_uid, // 새로 생성한 결제(재결제)용 주문 번호
                amount: body.amount,
                name: body.name
            }
        });
        const { code, message } = paymentResult.data;

        if (code === 0) { // 카드사 통신에 성공(실제 승인 성공 여부는 추가 판단이 필요함)
            if (paymentResult.data.response.status === "paid") { //카드 정상 승인
                res.send({ success: true });
            } else { //카드 승인 실패 (예: 고객 카드 한도초과, 거래정지카드, 잔액부족 등)
                //paymentResult.status : failed 로 수신됨
                res.send({ success: false });
            }
        } else { // 카드사 요청에 실패 (paymentResult is null)
            res.send({ success: false });
        }
    }
    catch (Err) {
        console.log("err : ", Err)
        res.send({ success: false })
    }
});

 

3. 구독에 대한 DB를 사용하는 서비스에 따라 설계하고 결제 요청 성공 시에 추가해주도록 한다.

( 저는 구독 남은 개월 수를 표시하고 나중에 아임포트 웹훅으로 카운팅 해주도록 설계했습니다. )

( 또한 쿠키를 삭제하고 로그인 유저의 구독에 관한 데이터를 변경하여 로그아웃 시킨 다음 로그인 했을 시에 서비스를 이용 가능하게 했습니다. )

        if (code === 0) { // 카드사 통신에 성공(실제 승인 성공 여부는 추가 판단이 필요함)
            if (paymentResult.data.response.status === "paid") { //카드 정상 승인
                Subscribe.create({
                    subscribe_muser_id: body.muser_id,
                    subscribe_type: body.radioItem + "개월 구독",
                    subscribe_customer_uid: body.customer_uid,
                    subscribe_merchant_uid: body.merchant_uid,
                    subscribe_count: body.radioItem,
                    subscribe_amount: body.amount,
                    subscribe_createdAt: new Date(),
                    subscribe_updatedAt: new Date()
                })
                // 사옹자의 구독에 관한 데이터를 가진 컬럼을 수정해준다. (로그아웃하고 로그인하면 쿠키에 등록되어 서비스 이용 가능)
                MUserInfo.update({
                    muser_subscribe_type: body.name
                }, {
                    where: {
                        muser_id: body.muser_id
                    }
                })
                res.clearCookie('사용했던 쿠키');
                res.send({ success: true });
            } else { //카드 승인 실패 (예: 고객 카드 한도초과, 거래정지카드, 잔액부족 등)
                //paymentResult.status : failed 로 수신됨
                res.send({ success: false });
            }
        } else { // 카드사 요청에 실패 (paymentResult is null)
            res.send({ success: false });
        }

 

혹시나 금액을 500 이하로 설정했을 경우 '[8105] 포맷에러(지불|신용카드|금액)'  이 에러구문을 볼 수 있다. 금액을 500원 이상으로 설정해주자.

728x90
반응형

+ Recent posts