[Nest.js] 토스 페이먼츠 결제 - 백엔드 혼자 구현해보기

초이지수·2023년 7월 20일
4

Nest.js

목록 보기
7/14
post-thumbnail

프로젝트 결제 기능을 구현 하기 위해 토스페이먼츠 결제 연동을 사용했다.

백엔드 기준에서 작성된 글입니다. 나중에 제가 다시 보려고 기록합니다!


🔹 토스페이먼츠 결제 연동

토스페이먼츠 결제 연동은 FE / BE 크게 두 부분으로 나눌 수 있다.

1. FE (프론트엔드) - 결제 요청 인증 위젯 연동하기
결제창을 띄우고 성공시 successUrl / 실패시 failureUrl 로 이동한다.

성공하면 successUrl에 paymentKey, orderId, amount 값이 포함되어 있다.

2. BE (백엔드) - 결제 승인 API 연동하기
FE가 넘겨준 payment, orderId, amount 값으로 토스 서버에 결제 승인 요청 보낸다.


📌 1. 리다이렉트 URL 처리 이해하기

결제를 요청하면 차례로 인증과 승인 과정이 진행된다.

  1. 인증 : 결제를 승인하기 전 결제 정보가 올바른지 검증하는 과정
  2. 승인 : 인증에 성공한 결제를 최종 승인하는 과정
    승인 요청에 성공하면 결제 요청 과정이 끝난다!

첫 번째 단계인 인증 결과를 받아 다음 단계로 넘어가기 위해 리다이렉트 URL처리가 필요하다. 토스페이먼츠에서 인증 결과를 URL의 쿼리 파라미터에 담아 리다이렉트하면, 상점(현재 내가 만든 서버)에서는 리다이렉트 URL을 받아 결제 승인을 처리할 수 있게 되는 것!

  • 첫 번째 단계인 인증에 성공한다면?
    성공 리다이렉트 URL(successUrl)에 쿼리 파라미터로 담긴 결제 정보를 이용해 승인 API를 호출한다.

  • 인증에 실패한다면?
    실패 리다이렉트 URL(failUrl)에 쿼리 파라미터로 담긴 에러 정보를 보며 디버깅한다.


📌 2. 우당탕탕 FE 없이 paymentKey 받아오기

토스 페이먼츠 결제위젯 SDK

SDK 준비 파트를 FE 분들이 구현해 성공한다면, paymentKey, orderId, amount 값을 보내주실 것이다. 이 값들을 BE가 받아서 처리하면 된다!


paymentKey, orderId, amonut는 뭔데??

토스 개발자센터 홈페이지에 보면 결제 요청 결과 확인을 발견할 수 있다.

결제 요청이 성공하면 requestPayment()의 파라미터로 설정했던 결제 성공 페이지(successUrl)로 이동합니다. 성공 리다이렉트 URL을 나타내는 success 뒤에 네 가지 쿼리 파라미터가 들어있습니다.

  1. paymentKey : 결제의 키 값
  2. orderId : 상점에서 발급한 주문 건 ID = requestPayment()에 담아 보낸 값
  3. amount : 실제로 결제된 금액
  4. paymentType : 결제 유형

💡 아하! 그럼 이제 paymentKey를 셀프로 받아보자!

처음에는 paymentKey가 클라이언트 키인줄 알았다.... 아후..나의 시간이여
paymentKey는 결제 위젯 SDK로 클라이언트 키로 요청을 주었을 때 토스에서 발급해주는 키다!


📎 2-1. 결제창을 띄워보자

토스페이먼츠 개발자센터에서 공유해준 html 코드를 이용해서 결제창을 띄워보았다!

  • public/toss.html
<head>
    <meta charset="utf-8" />
    <!-- 결제위젯 SDK 추가 -->
    <script src="https://js.tosspayments.com/v1/payment-widget"></script>
</head>
<body>
    <!-- 결제위젯, 이용약관 영역 -->
    <div id="payment-method"></div>
    <div id="agreement"></div>
    <!-- 결제하기 버튼 -->
    <button id="payment-button">결제하기</button>
    <script>
        const clientKey = 'test_ck_YZ1aOwX7K8m29JWqKLP3yQxzvNPG';
        const customerKey = 'G2MD0ghzwL7EwQH01EOfK'; // 내 상점에서 고객을 구분하기 위해 발급한 고객의 고유 ID
        const button = document.getElementById('payment-button');
        // ------  결제위젯 초기화 ------
        // 비회원 결제에는 customerKey 대신 ANONYMOUS를 사용하세요.
        const paymentWidget = PaymentWidget(clientKey, customerKey); // 회원 결제
        // const paymentWidget = PaymentWidget(clientKey, PaymentWidget.ANONYMOUS) // 비회원 결제
        // ------  결제위젯 렌더링 ------
        // 결제수단 UI를 렌더링할 위치를 지정합니다. `#payment-method`와 같은 CSS 선택자와 결제 금액 객체를 추가하세요.
        // DOM이 생성된 이후에 렌더링 메서드를 호출하세요.
        // https://docs.tosspayments.com/reference/widget-sdk#renderpaymentmethods선택자-결제-금액-옵션
        paymentWidget.renderPaymentMethods('#payment-method', { value: 300 });
        // ------  이용약관 렌더링 ------
        // 이용약관 UI를 렌더링할 위치를 지정합니다. `#agreement`와 같은 CSS 선택자를 추가하세요.
        // https://docs.tosspayments.com/reference/widget-sdk#renderagreement선택자
        paymentWidget.renderAgreement('#agreement');
        // ------ '결제하기' 버튼 누르면 결제창 띄우기 ------
        // 더 많은 결제 정보 파라미터는 결제위젯 SDK에서 확인하세요.
        // https://docs.tosspayments.com/reference/widget-sdk#requestpayment결제-정보
        button.addEventListener('click', function () {
            paymentWidget.requestPayment({
                orderId: '4TITFDV0Qqs0xzmtqqtjD', // 주문 ID(직접 만들어주세요)
                orderName: '토스 티셔츠 외 2건', // 주문명
                successUrl: 'http://localhost:3000/payments/success', // 결제에 성공하면 이동하는 페이지(간단하게 직접 만들었다)
                failUrl: 'http://localhost:3000/fail', // 결제에 실패하면 이동하는 페이지(어차피 에러니까 안 만들었다)
                customerEmail: 'customer123@gmail.com',
                customerName: '김토스',
            });
        });
    </script>
</body>
  • src/payments/payments.controller.ts

  • main.ts


📎 2-2 . http://localhost:3000/toss.html 접속하면?

짜잔 - ! 결제창이 뜬다! 실제 결제되는 거 아니니까 안심 주의

나는 휴대폰 결제(휴대폰 결제는 최소 300원 이상)로 요청을 보냈고, 결제 요청이 완료되면!


짜잔 - ! x2 내가 만든 success 페이지가 뜬다! url을 보면 success라고 뜬게 보인다!

http://localhost:3000/payments/success?paymentKey={PAYMENT_KEY}&orderId={ORDER_ID}&amount={AMOUNT}&paymentType={PAYMENT_TYPE}

url에 paymentKey값이 포함되어있다! 이제 paymentKey 값으로 백엔드 로직을 처리해주면 된다!


📌 3. 백엔드 스따뚜 - 결제 승인 API 호출


📎 2-1. 연동 준비하기

API 키

토스페이먼츠에 회원가입을 하지 않아도 테스트 키로 결제 연동을 할 수 있지만, 결제 내역을 확인할 수 없다. 일단 토스페이먼츠 회원가입을 하자!

회원 가입을 마쳤다면, 토스페이먼츠 개발자센터에 내 개발정보를 누르면 API 키를 확인할 수 있다. 키는 총 두 가지가 있다!


1. 클라이언트 키 - FE 분들이 SDK 연동을 위해 사용

2. 시크릿 키 - BE가 토스 서버로 요청을 보낼 때 사용 (토스페이먼츠 API Basic 인증)

클라이언트 키와 시크릿 키는 짝꿍이다! 항상 둘을 쌍으로 사용해야한다.
키 관련해서 에러가 계속 발생한다면 클라이언트 키와 시크릿 키가 짝꿍이 맞는지 확인해보자!

테스트 키는 실제 결제가 아닌 테스트용이다! 실제 결제를 하고 싶다면 사업자 등록 후 토스에게 라이브 키를 발급받아 사용해야한다!


API 키외에도 방화벽 설정이나 지원 브라우저 환경 확인 등 절차가 있었는데, 로컬 환경에서는 따로 설정을 안 해도 괜찮았다! (혹시 문제가 생긴다면 방화벽 설정, 지원 브라우저 환경을 확인해보세요)

배포 환경에서는 사업자 등록을 통해 발급 받은 테스트키 or 라이브키를 사용해야 된다고 합니다!


📎 2-2. Nest.js 코드 작성해보기

인증

토스페이먼츠 개발자 문서를 보면, 토스에서는 Basic 인증방식을 사용한다고 적혀져 있다. 시크릿 키를 사용할 때 콜론을 포함한 값을 Base64로 인코딩해서 사용해야한다.


  • src/payments/dto/payment.dto.ts

  • src/payments/payments.controller.ts

  • src/payments/payments.service.ts

여기서 주의할 점!은 시크릿 키를 보낼 때 발급 받았던 시크릿 키 뒤에 :를 추가하고 base64로 인코딩 해야된다는 것이다! service 코드를 살펴보면 Authorization에 인코딩을 위한 코드가 적혀있다!


📎 2-3. Postman 요청

코드를 다 작성한 후, Postman으로 요청을 보내보자!

성공쓰!


🖇 (번외) Nest.js 코드 없이 토스페이먼츠에 결제 승인이 잘 되는지 확인해보기

Nest.js 코드 없이 curl 명령어를 통해 토스페이먼츠에 결제 승인이 잘 되는지 확인하려면, 시크릿 키를 직접 인코딩 한 후에, 토스페이먼츠 url로 요청을 보내야한다.

- 인코딩된 값 얻는 명령어

echo -n '시크릿키:' | base64

- toss로 직접 결제 승인 요청 보내는 curl 명령어

 curl --request POST \
  --url https://api.tosspayments.com/v1/payments/confirm \
  --header 'Authorization: Basic {인코딩 한 시크릿 키}' \
  --header 'Content-Type: application/json' \
  --data '{"paymentKey":"{아까 받은 paymentKey}","amount":{요청 보냈던 결제 금액},"orderId":"{요청 보냈었던 orderId}"}'

📌 결제 승인까지 확인!


토스페이먼츠 개발자센터


😉 결제 해보니 어때..?

휴.. 공식문서랑 언제쯤 친해질 수 있는 걸까? 공식 문서에 정답이 다 있는 걸 알지만 그 정답까지 가는 시간이 조금.. 걸린다. 내가 바로 공식 문서가 되는 그날까지.. 많이 읽어보는 게 답이겠죠? 오열

공식 문서를 읽다 보면 나라면 이렇게 작성했을 텐데.. 라는 부분도 보이고! 문서화에 대해 자연스럽게 고민해 보게 되는 것 같아 또 다른 배움이 있었다!

토스페이먼츠는 공식 문서가 친절하게 나와 있는 편이었따! 감사합니다 🙇‍♀️
그런데 왜 테스트 환경에는.. 신한카드가 없는 거죠? 저는 신한카드 뿐인데,,🥹


📍 (+추가) 결제 승인까지 완료했으니 결제 취소도 해보자!

토스페이먼츠 결제 취소 GitHub

profile
닫혀 있어서 벽인 줄 알고 있지만, 사실은 문이다.

2개의 댓글

comment-user-thumbnail
2023년 12월 24일

글을 읽다가 궁금한게 있어서 질문드립니다. 프론트(public/toss.html)에서 스크립트 태그 실행을 하게 되고 성공을 한다면 successUrl에 명시된 url로 이동되어서 paymentKey가 쿼리 스트링에 표시되는것은 알겠습니다. 그리고 이 paymentKey를 백엔드로 보낸 후 백엔드에서 토스 api에 ajax 요청을 보내는 것 까진 알겠어요. 이해가 안가는게 있는데
질문. 1 successUrl에 이동된것이 완전히 결제가 이루어진 상태인가요?
질문. 2 만약 결제가 이루어진 상태가 아니라면 paymentKey를 백엔드에 보내지 않은 상태인거 같은데 successUrl 페이지에 있는 스크립트 어딘가에서 백엔드로 ajax 요청을 보내나요?
질문. 3 src/payments/payments.service.ts이 파일에서 13 ~ 35 줄 사이에서 axios를 통한 ajax 통신이 이루어지는데 제가 알기로는 axios가 응답이 200이 아니라 400에서 500이라면 에러로 반환되어서 캐치로 가게 될것입니다. 그리고 응답으로 { title: "결제 성공", body: response.data }로 이런식으로 응답을 보내는데 저 응답이 보내지는 상황이라면 무조건 결제가 성공된것일까요?
답변을 해주시면 감사하겠습니다!

1개의 답글