2023. 4. 20

Junghan Lee·2023년 4월 20일
0

TIL Diary

목록 보기
39/52

Index

결제 프로세스(역사, 카드사, PG, 결제솔루션, 포인트)
외부API(아임포트) 사용
이벤트 루프(시간관련 이벤트)

Intro


프론트엔드에서 만든 시간? 속고 있다?
이벤트 루프와 await의 원리

결제 프로세스

인터넷에서 결제를 통해 포인트를 충전하는 일반적인 과정
1) 브라우저에서 충전하기 버튼 클릭
2) 충전하기 창에서 원하는 금액 선택, 결제 진행
3) 개인 결제수단(카드, 카카오페이 등)에서 빠져나간 금액만큼 포인트 증가

결제의 역사

카드사 )
각각의 카드사에게 승인받아 결제 시스템 구축, 은행 및 카드사마다 지원하는 결제 방법이 모두 다르기 때문에 모든 경우를 고려해 시스템을 개발하려면 비용이 크므로 실무에서 이런 방식으로 개발하기는 불가능에 가깝다.

PG(Payment Gateway) )
각 카드사와 연결하는 작업을 대신해주는 PG사를 통해 결제 시스템을 구축하는 방식으로 NHN, 나이스페이, 이니시스 등이 있다. 이 회사들이 카드사들과 미리 의논해 결제를 연결하는 방법을 정해놓고 통합해 관리할 수 있는 대행 시스템을 구축해 놓아서 해당 PG사의 가이드에 맞추어 결제 시스템을 개발하면 여러 개의 카드 회사들에 맞추어 여러번의 개발을 진행할 필요가 없어진다.

일반적으로 PG사의 개발 가이드는 수백페이지 가량의 볼륨을 가진 pdf파일로 전달되며, asp, php, jsp 등의 언어로 작성되어 있다. 이 가이드를 활용해 필요한 기능을 구현하면 되지만 이 작업 또한 상당한 시간이 소요된다.

결제 솔루션)
PG사의 가이드에 맞추어 결제 시스템을 구축하는 작업을 대신 해주는 결제 솔루션 업체도 등장했다.

PG사마다 다른 개발 가이드를 가지고 있다 보니 한 업체와 거래하는 시스템을 구축한 뒤에는 다른 업체로 옮기기가 어렵다. 이러한 문제점을 보완해주는 업체가 바로 결제 솔루션(결제 API) 업체다.

결제 솔루션 업체를 이용하면 개발환경과 상관없이 원하는 PG사와의 결제시스템을 연결시킬 수 있다.

대표적인 결제 솔루션 업체는 아임포트, 부트페이 등이 있다.

중견, 중소 규모 기업의 경우 PG사와 직접 계약하는 것 보다 결제 솔루션을 이용하는 것이 효율적.

포인트 충전 과정
아임포트를 이용하는 경우 사용자가 결제를 통해 포인트를 충전하는 과정은
1) 브라우저에서 충전하기 버튼 클릭
2) 충전하기 창에서 원하는 금액 선택 및 결제 클릭
3) 아임포트에서 제공하는 rest-api를 이용해 결제 요청
4) 결제 성공 시 해당 결제에 대한 imp_uid와 결제 금액 등의 데이터를 돌려받음
5) 성공한 결제 정보를 백엔드 서버로 mutation
6) db의 유저 포인트 정보에 결제 내역 업데이트

외부 API(아임포트) 사용하기

초기설정

홈페이지(가입) https://www.iamport.kr/

관리자 페이지에서는 아임포트 사용을 위한 기본 설정부터 연결해준 PG사의 정보, 실제 결제내역까지 확인이 가능하며 아임포트에서 제공해주는 Rest-API들의 가이드 문서 역시 관리자 콘솔에서 확인 가능

사용을 위해 먼저 사용할 PG사를 선택해야 한다.

관리자 페이지에서 시스템 설정 - PG설정(일반 결제 및 정기 결제) 페이지로 이동

PG설정 페이지에서 결제에 필요한 PG사를 설정할 수 있으며 PG사 선택 창을 클릭하면 여러 종류의 PG 사들이 나타남

그중 KG이니시스(웹표준결제창) 을 선택해 보자.

위의 화면과 달리 기본 PG사가 없으면 왼쪽 목록의 PG사 추가 버튼으로 추가해야 한다.

PG설정으로 PG사를 설정하면 앞으로 이루어지는 모든 결제 과정에서 해당 PG 사로 연결되어 결제가 이루어진다.

테스트모드를 첵크하지 않으면 실제 결제가 이루어지므로 주의!(KG이니시스의 PG사 테스트의 경우 결제시 계좌에서 금액이 출금되나 당일 자정 이전에 모두 결제가 취소되어 환급됨)

웹훅노티피케이션

가상 계좌를 이용한 무통장 입금의 구현?
1) (사용자가) 무통장 입금 선택
2) 아임포트에서 제공하는 API를 통해 가상 계좌 생성
3) 결제중이던 웹브라우저를 실행 종료하고 가상 계좌를 발급한 은행 서비스에 접속
4) 가상 계좌 유효기간 내에 입금 완료

그러나 이 단계에서 문제가 발생하는데 브라우저에서 결제가 이뤄지는 것이 아니기 때문에 결제 완료 시점에 결제된 금액과 imp_uid를 받아 백엔드로 보내주는 것이 불가능하다. 이 작업을 위해 아임포트는 웹훅 노티피케이션을 제공한다.

웹훅노티피케이션이란 특정 이벤트가 발생했을 때 타 서비스나 응용 프로그램으로 알림(notification)을 보내는 기능으로 아래와 같은 기능을 구현 가능하다.
1) 가상계좌 - 무통장 입금
2) 결제 취소
3) 예약 결제
4) 환불

DOCS(webhook) https://docs.iamport.kr/tech/webhook
Rest-API 가이드 https://api.iamport.kr/

아임포트 적용

이 내용은 아임포트 DOCS 페이지와 Github에도 잘 설명되어 있다.
Docs : https://docs.iamport.kr/implementation/payment
Github : https://github.com/iamport

아임포트를 프로젝트 내에서 직접 사용하기 위해서는 아임포트 라이브러리를 head 부분에 추가해 주어야 한다.

Next.js의 경우 head태그를 import해 이용한다.

import Head from 'next/head';
// 최상단에 next/head 의 Head 태그 호출

<Head>
	<script type="text/javascript" src="https://code.jquery.com/jquery-1.12.4.min.js" ></script>
	<script type="text/javascript" src="https://cdn.iamport.kr/js/iamport.payment-1.1.5.js"></script>
</Head>

아임포트 라이브러리를 추가하기 위한 스크립트 태그는 앞서 import한 헤드 태그로 감싸 결제 기능을 사용할 페이지의 return 안에 추가하면 된다.

라이브러리 추가한 뒤 사용하려는 아임포트의 가맹점 식별 코드를 설정해주어야 하며 불러온 아임포트는 window.IMP를 통해 제어할 수 있다.

가맹점 식별 코드는 아임포트 관리자 페이지의 시스템 설정 - 내 정보 - 가맹점 식별 코드

이 코드를 window.IMP.init에 넣어 설정하면 아임포트의 결제창을 불러올 수 있다.


  IMP.request_pay({
    pg: "html5_inicis",
    pay_method: "card",
    //merchant_uid: "결제 번호",
    name: "아이리버 무선 마우스 외 1개",
    amount: 10000,
    buyer_email: "이메일@gmail.com",
    buyer_name: "홍길동",
    buyer_tel: "010-4242-4242",
    buyer_addr: "서울특별시 강남구 신사동",
    buyer_postcode: "01181",
		m_redirect_url : "/"
		
  }, function (rsp) { // callback
				if (rsp.success) {
           alert('결제가 성공했습니다.');
            // 결제 성공 로직

        } else {
            alert('결제에 실패했습니다.');
            // 결제 실패 로직
        }
   });

위 코드를 IMP.init("")으로 식별 코드를 설정했던 페이지 안에 새로 추가해준다.

이 코드를 추가하면 아임포트 관리자 페이지에서 설정해놓은 PG사의 결제 페이지를 프로젝트에서 사용할 수 있게 되며 결제의 성공, 실패 여부에 따라 실행되는 로직을 만들어줄 수도 있따..

결제 파라미터에서 pg나 pay_method의 부분에서는 어떤 PG사를 이용할 건지, 어떤 결제 방식을 사용할 건지 선택이 가능하며 name, amount에는 주문 이름, 결제 금액, buyer에는 구매자의 정보를 추가로 넣어줄 수 있다.

결제 파라미터의 정보 : https://docs.iamport.kr/tech/imp


모두 적용 후 위와 같이 프로젝트에 설정된 PG사의 결제 페이지가 오픈된다.

위처럼 결제 과정까지 성공하면 성공했을 때 설정했던 로직이 실행되며 반대의 경우도 마찬가지다.

프로세스에 문제가 없는데 결제에 실패하는 경우, 결제 금액이 100원 미만이면 불가능하다는 점을 참고한다.

결제가 성공하면 아임포트로부터 해당 결제에 대한 response를 받아올 수 있으며 여기엔 해당 결제에 대한 imp_uid와 결제 금액 등의 정보가 담겨 있따.

이 rsp를 이용해 포인트 충전 API에 결제 정보를 넘겨줄 수 있다.

결제 내역
아임포트 관리자 페이지의 결제승인내역 페이지에서 현재까지의 결제 내역(실 및 성공) 등을 확인할 수 있다. 또한 결제내역 페이지에서는 결제된 내역에 대한 정보를 볼 수 있고 'imp_'로 시작되는 고유한 결제 ID또한 확인할 수 있다.

성공한 내역에 한해 수동 취소 또한 가능하다.

백엔드 API로 데이터 넘겨주기

우리는 앞서, 결제에 성공하면 아임포트로부터 해당 결제에 대한 rsp를 받아올 수 있다고 했습다.
rsp에는 해당 결제에 대한 imp_uid와 결제 성공 여부, 결제 금액 등의 정보가 담겨 있으며
rsp를 이용하여 포인트 충전 API에 결제 정보를 넘겨줄 수 있다.

결제 성공 시, rsp에는 다음과 같은 정보가 담겨 들어온다.
그 중 필요한 요소들을 활용하여 백엔드로 mutation을 요청하면
DB에 있는 User 정보에 결제 내역을 반영할 수 있다.

모바일 결제
모바일 결제의 화면은 조금 다르다.


모바일 화면으로 접속한 상태에서 아임포트 결제를 시도해보면, 결제 화면으로 넘어가며 아예 페이지의 url이 바뀐 것을 확인할 수 있다.

그렇기 때문에 반응형 웹 페이지를 작업할 때에는 모바일에서 결제가 완료된 후 돌아올 url까지 연결을 해줘야 한다.

m_redirect_url이라는 파라미터를 이용해서 돌아올 url을 지정할 수 있다.


하지만 m_redirect_url을 이용해서 페이지를 이동할 경우,
해당 결제 성공시 실행하도록 설정해놓은 함수를 잃어버리게 되므로

이 때에는 **웹훅노티피케이션**을 이용해 결제의 결과값을 백엔드로 전달해줄 수 있다.

모바일 결제 정보 DOCS https://docs.iamport.kr/implementation/payment#mobile-web

시간 관련 이벤트

결제를 완료해서 백엔드로 createPointTransaction 등의 API 요청을 보낼 때

결제가 이루어진 시점의 시간을 db에 함께 저장하게 된다.

이 때, 이 **시간을 새로 생성하는 작업**절대 프론트엔드 서버에서 하면 안됨

사용자가 PC의 시각을 실제와 다르게 조작해서 사용하는 경우, 프론트엔드 서버에서 생성한 시간 역시 조작된 시각을 따라가기 때문이다.

그래서 시간을 생성하는 작업은 반드시 백엔드에서 이루어져야 한다.

또한, 이 시간은 통상적으로 국제 표준시인 UTC 시간을 이용함!

시간 처리 관련 라이브러리

Moment.js https://momentjs.com/

정기결제 크론 탭 이해

사이트에서 이벤트 할인 상품을 판매한다고 가정해 보자

자정이 지나면 이벤트를 마감하고 이벤트 가격에 더 이상 제품을 구매할 수 없도록 막아야 한다.

이것을 구현하는 가장 간단한 방법은 관리자 페이지에 이벤트 마감 버튼을 만들어두는 것이다. 이벤트 마감 버튼을 누르면 더 이상 이벤트 상품을 구매할 수 없도록 막는 것.

하지만 이 경우 이벤트 종료 시간까지 담당자가 대기하고 있어야 하는 데다가, 실수로 이벤트 종료 시간보다 이른 시간이나 늦은 시간에 버튼을 누르게 될 가능성이 있다.

어떻게 하면 정확하고 편리하게 이러한 시간 관련 제어를 할 수 있을까?

이런 경우 시간 관련하여 이벤트를 발생 시켜준다.

이벤트 발생시키는 방법
1. 클릭, 내용 입력 등 직접 이벤트 발생
2. 특정 시간에 자동으로 이벤트 발생

2번과 같은 작업을 해주는 기능을 **크론탭(Crontab)**이라고 한다.

크론탭을 이용하면 관리자가 정해진 시간까지 대기하거나, 그 시간에 클릭 등의 이벤트를 직접 발생시킬 필요가 없다.(참고로 리눅스기반 OS에는 기본 설치)

이벤트 루프

// setInterval(() => {
//   document.getElementById("timer")?.innerText = "2:59";
// }, 1000);

export default function TastQueuePage(){

	const onClickTimer = () => {

		console.log("=======시작~~=======")

		setTimeout(() => {
			console.log("1초 뒤에 실행된답니다 😎️")
		}, 1000)

		console.log("=======끝~~=======")
	}

	return <button onClick={onClickTimer}>시작~~</button>;
}

위는 3분짜리 타이머

코드 순서대로 라면 함수의 끝 부분의 ‘끝’ 콘솔보다 setTimeout 안에 있는 콘솔이 먼저 실행되어야 할 것 같은데, 순서가 뒤집혀 있다.

검증을 위해 setTimeout을 0초로 바꾸어 다시 한 번 실행해 보자.

export default function TastQueuePage(){

	const onClickTimer = () => {

		console.log("=======시작~~=======")

		setTimeout(() => {
			console.log("0초 뒤에 실행된답니다 😎️")
		}, 0)

		console.log("=======끝~~=======")
	}

	return <button onClick={onClickTimer}>시작</button>;
}

콘솔이 찍히는 순서에는 변화가 없는데 이는 자바스크립트의 동작 원리 때문이다.

Task Queue
위의 타이머 코드는 콜스택에 이렇게 쌓이게 된다.

실행순서
1.callStack에서 onClickTimer 함수가 실행 (Stack - Last In First Out / LIFO 구조)
2.Background에 setTimeout()을 보내서 실행
3.setTimeout()이 TaskQueue로 전달되어 쌓임. (Queue - First In First Out / FIFO 구조)
4.TaskQueue에 쌓이는 함수는 CallStack이 다 비워진 다음 가장 마지막에 실행

위와 같은 순서에 따라 onClickTimer 함수 내에서 setTimeout이 가장 마지막에 실행되게 된다.

여기서 TaskQueue에 있는 함수를 CallStack으로 보내는 역할을 하는 일꾼이 있는데 그것을 **스레드(Thread)**라고 한다.

싱글 스레드
자바 스크립트는 **싱글 스레드** 방식을 가지고 있다. 조금 더 구체적으로 표현하자면 **싱글 이벤트 루프 스레드**라고도 한다.

핵심은, CallStack이 비어야만 TaskQueue에 있는 작업을 CallStack으로 가져온다는 것

CallStack이 작업 중이라면 setTimeout에 설정한 1초가 이미 지났다고 하더라도 TaskQueue 안의 작업이 CallStack으로 들어오지 못한다.

다음과 같은 코드를 입력하고 yarn dev 해보자.

export default function TastQueuePage(){

	const onClickTimer = () => {

		console.log("=======시작!!!!=======")

		setTimeout(() => {
			console.log("1초 뒤에 실행될 거예요!!!")
		}, 1000)

		for(let i=0; i<=9000000000; i+=1){
			sum = sum + 1
		}

		console.log("=======끝!!!!=======")
	}

	return <button onClick={onClickTimer}>시작!!!</button>;
}

페이지가 그려진 뒤 콘솔을 열고 시작 버튼을 눌러보면 클릭 후 1초가 훨씬 지났는데도 ‘끝’ 콘솔이 뜨지 않는 것을 확인할 수 있다.

setTimeout이 TaskQueue에 들어가서 대기하고 있지만, 반복문이 종료되어 CallStack이 비워지지 않으면 TaskQueue에 있는 setTimeout은 실행되지 못하기 때문이다.

setInterval, setTimeout처럼 CallStack에 쌓이지 않고 Background, TaskQueue로 넘겨지는 작업을 **비동기 작업**이라고 한다.

axios도 비동기 처리가 이루어지는 대표적인 라이브러리 중 하나.

프로세스와 스레드

프로세스:실행되어있는 프로그램
스레드: 프로세스 안에서 동작하는 일꾼

스레드가 하나인 언어를 싱글 스레드 언어, 여러개인 언어를 멀티 스레드 언어라고 한다.(자바스크립트 : 싱글 스레드)

멀티 스레드와 싱글 스레드

참고) 컨텍스트 스위칭 (멀티스레드에서 발생)

멀티 스레드 언어의 경우 동시에 여러가지 작업을 처리하고 있는 것처럼 보이지만,

사실은 여러 개의 스레드가 여러가지 작업을 번갈아가며 빠르게 수행하고 있는 것과 같다.

멀티 스레드의 경우에도 하나의 요청에 대한 응답을 기다렸다가 다음 작업으로 이동해야 하는 것은 동일하다. (이러한 작업을 컨텍스트 스위칭이라고 함.)

그렇기 때문에 멀티 스레드라고 해서 싱글 스레드보다 두드러지게 빠르지는 않다.

오히려 자바스크립트와 같은 **이벤트 루프 싱글 스레드**의 경우

오래 걸리는 작업을 TaskQueue로 빼서 처리하기 때문에 높은 퍼포먼스를 낼 수 있다.

블로킹 vs 논블로킹
위에서 설명한 멀티 스레드 예시처럼

하나의 요청에 대한 응답이 와야만 다음 작업을 시작할 수 있는 방식을

**블로킹(blocking) 방식**이라고 한다.

그리고 이벤트 루프 싱글 스레드처럼 하나의 작업이 진행되는 동안 시간이 오래 걸리는 작업은 따로 마련한 공간에 던져 실행하는 방식을 **논블로킹(non-blocking) 방식**이라고 한다.

자바스크립트 : 싱글 스레드 & 논블로킹

profile
Strive for greatness

0개의 댓글