Promise 실전에서 사용해보기

by 토스페이먼츠

Promise 실전에서 사용해보기

비동기 작업이란 특정 코드의 로직이 끝날 때까지 기다리지 않고, 나머지 코드를 먼저 실행하는 것이에요. 웹사이트 개발에는 비동기 작업을 자주 사용해요. 서버에서 데이터를 불러올 때 오래 걸릴 수도 있는데, 그동안 다른 코드를 실행하지 않고 가만히 기다리면 웹 사이트를 로딩하는 게 굉장히 오래 걸리기 때문이죠.

하지만 순서대로 불러야 하는 코드가 있으면 어떤 일이 일어날까요? 예를 들어, 연산이 끝나기도 전에 연산 결과를 파라미터로 사용하는 함수를 실행하면 에러가 나겠죠. 그래서 이런 비동기 작업을 순차적으로 실행하기 위해 JavaScript에서는 콜백 함수를 사용해요. 콜백 함수는 특정 로직이 끝났을 때 원하는 코드를 실행할 수 있어요. 하지만 콜백에 또 콜백을 계속 호출하게 되면 코드가 복잡해지고 에러도 처리하기 어려워요.

이런 단점은 Promise를 사용해서 해결할 수 있어요. 오늘은 Promise가 무엇이고 어떻게 사용해야 되는지 토스페이먼츠 결제위젯 예제로 알아볼게요.

Promise란?

Promise는 비동기 함수가 반환하는 객체에요. 함수의 성공 또는 실패 상태를 알려줘요. 콜백을 직접 호출하는 방법 대신, Promise로 콜백을 부를 수 있어요. 이런 특징 때문에 Promise를 사용하면 비동기 처리 시점, 비동기 함수의 결과를 쉽게 확인할 수 있고 에러도 어디서 일어났는지 파악하기 편리해요.

Promise를 생성하는 방법

토스페이먼츠 결제위젯 SDK를 사용하면 requestPayment() 메서드로 결제를 요청해요. 아래 코드는 requestPayment() 메서드의 구조를 보여주는데요, 리턴 값으로 Promise를 생성하고 있어요.

결제가 요청되기 전에는 Promise가 대기(Pending) 상태에요. 결제 요청에 성공하면, Promise 생성 함수에 있는 resolve() 메서드가 호출돼요. Promise가 성공 상태로 바뀌고 data 값을 가지게 돼요. 성공 상태를 가진 Promise는 fulfilled된 상태라고 말해요. 하지만 결제 요청에 문제가 있다면 reject()를 호출해서 Promise를 실패 상태로 바꾸고 error 데이터를 가지게 돼요. 실패 상태를 가진 Promise는 rejected된 상태라고 말해요.

function requestPayment(paymentData) {
	// ...
	return new Promise((resolve, reject) {    // Pending 상태
		if(isSuccess) {
			resolve(data)                         // 성공 상태
		}
		else {
			reject(error)                         // 실패 상태
		}
	})
}

정리하자면, Promise는 세 개의 상태를 가질 수 있어요.

  • 대기(Pending): 비동기 함수가 아직 시작하지 않은 상태
  • 성공(Fulfilled): 비동기 함수가 성공적으로 완료된 상태
  • 실패(Rejected): 비동기 함수가 실패한 상태

Promise를 처리하는 방법

결제 요청의 결과에 따라 성공 또는 실패 상태의 Promise가 반환된다는 걸 위에서 봤는데요. 토스페이먼츠 결제위젯 예제로 Promise를 처리하는 방법도 알아볼게요.

Promise를 처리할 때는 then() 또는 catch() 메서드를 사용할 수 있어요. 각 메서드 파라미터에는 콜백 함수를 넣는데요. 결제 요청에 성공 상태의 Promise가 반환되면 then() 메서드가 호출돼요. 반대로 실패 상태의 Promise가 반환되면 then() 메서드를 건너뛰고 catch() 메서드가 바로 호출돼요.

아래 코드를 살펴볼게요. requestPayment()가 반환하는 Promise를 처리하기 위해 then()catch()를 연결해놓은 모습이에요.

  • 결제 요청이 성공하면 then() 메서드가 실행돼요. 아래 코드는 성공 상태의 Promise가 가진 data를 콘솔에 출력해요. 실제 결제 연동에서는 data 값으로 결제 승인 API를 호출하세요. 그럼 결제가 최종적으로 완료돼요.
  • 결제 요청이 실패하면 바로 catch() 메서드가 실행돼요. then() 메서드는 실행되지 않아요. 실패 상태의 Promise가 갖고 있는 error 값을 불러오고 있어요. 아래 코드는 NEED_CARD_PAYMENT_DETAIL 에러가 발생할 때 에러 메시지를 콘솔에 로그해요. 이렇게 catch() 블록에서는 각 에러 코드를 처리하는 로직을 추가하면 좋아요.
paymentWidget.requestPayment({
  orderId: "t9JI0Bs1SVdJxRs8yjiQJ",            
  orderName: "토스 티셔츠 외 2건",                    
})
.then(function (data) {
  console.log(data);
})
.catch(function (error) {
  if (error.code == "NEED_CARD_PAYMENT_DETAIL") {
    console.log(error.message);
  }

결제 연동에서 Promise 주의점

기본적으로 Promise를 생성하고 처리하는 방법을 알아봤는데요. 토스페이먼츠 결제위젯 SDK에서 Promise를 사용할 때 다음 세 가지를 주의하세요.

✅ 리다이렉트 파라미터를 설정할 필요가 없어요

결제를 요청하는 requestPayment() 메서드에는 successUrl, failUrl 파라미터가 있는데요. 구매자가 카드사・은행 앱에서 결제 인증을 완료하면 결과에 따라 자동으로 sucessUrl, failUrl로 이동해요. 이걸 리다이렉트 방식이라고 불러요. 하지만 Promise는 페이지가 이동하면 사용할 수 없어요. Promise를 사용할 때는 두 파라미터를 설정하지 마세요.

✅ Promise는 PC에서만 사용하세요

토스페이먼츠 결제 연동할 때는 PC 환경에서만 Promise를 사용할 수 있어요. 모바일 환경에서는 구매자가 카드사・은행 앱으로 이동하기 때문에 Promise를 받을 수 없어요. 모바일 환경에서는 반드시 리다이렉트 방식을 사용하세요.

✅ iframe을 사용하세요

requestPayment() 메서드에서 windowTarget 파라미터로 결제창이 열리는 프레임을 설정할 수 있어요. 기본값은 iframe 이지만, self도 사용할 수 있어요. self로 파라미터를 설정하면 브라우저가 결제창으로 이동하기 때문에 Promise를 정상적으로 받을 수 없어요. Promise를 사용할 때는 iframe으로 설정하거나, windowTarget파라미터를 안 넘기면 돼요.

Promise 직접 사용해보기

지금까지는 실행이 불가능한 코드로 Promise의 작동 방법을 알아봤는데요. 아래 코드는 테스트 환경에서 직접 실행해볼 수 있어요.

결제위젯에서 카드사를 선택하지 않고 ‘결제하기’ 버튼을 누르면 NEED_CARD_PAYMENT_DETAIL 에러가 발생해요. 아래 그림의 왼쪽 화면처럼 콘솔에 “카드 결제 정보를 선택해주세요.”라는 에러 메시지가 출력돼요.

결제위젯에서 결제수단을 선택하고 정상적으로 결제를 완료해보세요. 실제로 결제는 이뤄지지 않아요. 아래 그림의 오른쪽 화면처럼 결제 요청이 완료되면 콘솔에 paymentKey, orderId, amount, paymentType이 들어간 객체가 보일거에요. 해당 정보로 결제 승인 API를 호출하면 결제가 최종적으로 완료돼요.

<head>
  <meta charset="utf-8" />
  <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_D5GePWvyJnrK0W0k6q8gLzN97Eoq"
    const customerKey = "mOOpaZ2F43o0asvyfol1x" // 내 상점에서 고객을 구분하기 위해 발급한 고객의 고유 ID
    const button = document.getElementById("payment-button")

    // ------  결제위젯 초기화, 렌더링 ------ 
    const paymentWidget = PaymentWidget(clientKey, customerKey)
    paymentWidget.renderPaymentMethods(
      "#payment-method", 
      { value: 15000 }
    )
    paymentWidget.renderAgreement('#agreement')

    // ------ '결제하기' 버튼 누르면 결제창 띄우기 ------
    button.addEventListener("click", function () {
      paymentWidget.requestPayment({
        orderId: "t9JI0Bs1SVdJxRs8yjiQJ",            
        orderName: "토스 티셔츠 외 2건",                 
        // successUrl: "https://my-store.com/success",  
        // failUrl: "https://my-store.com/fail",        
				// windowTarget: "self"
      })
			.then(function (data) {
        console.log(data);
      })
      .catch(function (error) {
        if (error.code == "NEED_CARD_PAYMENT_DETAIL") {
          console.log(error.message);
        }
      })
    })
  </script>
</body>

함께 읽으면 좋을 콘텐츠

📍 결제 요청, 인증, 승인… 이게 다 뭔가요?

📍 30분 안에 결제 페이지 개발하기 (ft. 결제위젯)

📍 React로 결제 페이지 개발하기 (ft. 결제위젯)

Writer 박수연 Graphic 이은호, 이나눔

    의견 남기기
    토스페이먼츠

    고객사의 성장이 곧 우리의 성장이라는 확신을 가지고 더 나은 결제 경험을 만듭니다. 결제가 불편한 순간을 기록하고 바꿔갈게요.