실제 정기결제 기능 구현을 위해 포트원 sdk를 사용해서 kg 이니시스 정기결제를 구현했습니다.
저만 그런지는 모르겠지만 공식 문서에 자세히 설명을 해줘도 그것만으로는 이해가 잘 안되기도 하고 포트원은 설명해주는 문서가 참 많은데 묘하게 말이 조금씩 다르다거나 일관적이지 않아서 여러 문서들과 블로그들을 찾아보며 구현한 결과를 작성했습니다. (버전은 V1 사용)
글을 작성하기 전에, 프로젝트 용도가 아니라 실제 서비스에서 결제 기능을 사용하시는 분들은 최대한 미리 결제 기능을 구현해두시는걸 추천드립니다.
일단 포트원 sdk 결제창을 사용하기 위해 필요한 값들이 있는데, 이 값들은 포트원 관리자 콘솔에 사업자 정보를 등록하고 결제대행사 신청을 완료해야 발급받을 수 있습니다.
그리고 결제 기능을 구현한 후 결제대행사에서 심사를 받아야 실제 결제를 사용할 수 있는데, 한 번에 통과하지 못하면 피드백을 진행하여 재심사를 받아야 해서 생각보다 오랜 시간이 소요될 수 있으니 실제 서비스 런칭 날짜에 맞추어 개발 일정을 계획하시는걸 추천드립니다.
포트원에서 정기결제 기능을 구현하려면 인증 결제가 아닌 비인증 결제를 연동해야 합니다.
비인증 결제는 카드 정보를 입력하여
(1) 일회성 결제를 하거나
(2) 빌링키를 발급받은 뒤 판매자가 원하는 시점에 고객의 별도 인증 없이 결제를 요청
하는 두 가지 방식이 있는데 (2) 가 정기결제에 해당합니다.
우선 빌링키란 구독형 정기결제, 종량제 과금결제 등 구현 시 원하는 시점에 재 결제를 진행할 수 있는 결제용 암호화 키입니다.
고객사는 고객의 카드 정보를 소유할 수 없습니다. 그렇기 때문에 카드사로부터 고객이 결제하기 위해 등록해둔 카드와 매칭되는 빌링키를 발급받아 저장하고, 이후 원하는 시점에 저장해둔 빌링키로 결제를 청구하기 위해 사용하게 됩니다.
즉, 정기결제 기능을 구현하려면 비인증 결제 연동 - 빌링키 결제 문서를 참고해야함
(공식문서에 다 나와있지만 링크 타고 옮겨가기 귀찮으니 여기에 받아적어둡니다)
https://developers.portone.io/opi/ko/integration/start/v1/non-auth?v=v1
비인증결제 연동하기
카드 정보를 직접 입력하여 일회성 결제 또는 빌링키를 발급 및 정기결제 이용 방법을 안내합니다.
developers.portone.io
빌링키를 발급하는 방식에는 REST API 방식과 결제창(SDK) 방식 두 가지가 존재합니다.
먼저 REST API 방식은 고객사가 원하는 형태의 화면으로 카드정보 입력란을 커스텀했을 때 사용하는 방식입니다.
예를 들어보면, input 태그와 form 태그를 사용해서 로그인 페이지를 직접 구현하는 것 처럼 카드정보 입력창이나 페이지를 직접 구현했을 때가 이 경우에 속하게 됩니다. 저의 경우 REST API 방식은 카드사에서의 심사가 까다롭기도 하고 고객사의 요청이 없었기 때문에 포트원 sdk를 사용한 결제창 방식으로 진행했습니다.
저는 처음에 포트원 정기결제가 어떻게 진행되는지 정확히 파악하는 데 오랜 시간이 걸렸기 때문에, 결제연동을 시작하기 전에 진행 과정을 간략하게 적어보자면 아래와 같이 진행됩니다.
고객이 처음 결제창을 열어 카드 정보를 입력 후 확인 버튼 클릭
-> 빌링키가 발급됨
-> 프론트엔드에서 서버에 빌링키 발급 성공시 해당 결제 정보를 전달
-> 전달받은 값을 사용해 서버에서 비 인증 결제(빌링키) API인 '/subscribe/payments/again' 에 결제 요청
-> 결제 완료(고객의 카드에서 실결제가 이루어짐)
-> 서버에서 결제 예약 API 에 해당 날짜의 결제 예약 요청
-> 해당 날짜마다 정기결제가 진행됨
위의 과정을 보면 사실상 프론트엔드가 해야하는 것은 IMP.request_pay() 함수를 작성하여 결제창이 열리도록 구현한 후 고객이 카드 정보를 입력하여 빌링키가 발급되면 서버에 해당 결제 정보를 전달하는 것 뿐입니다. 필요한 경우 결제 완료를 알려주는 페이지로 리다이렉트하는 코드를 추가하면 됩니다.
이제 제가 직접 해본대로 포트원 정기결제 과정을 작성해보겠습니다.
1. 포트원 관리자 콘솔 가입 후 결제대행사 결정하여 계약하기
3-2 결제대행사별 연동 정보 확인하기 를 클릭하면 자세한 설명이 나와있으니 참고하면 됩니다.
그리고 아래의 값들을 메모해두세요(제가 작성한 예시는 kg이니시스에 해당합니다)
- PG상점아이디(MID): 결제대행사 계약 후 받은 상점 아이디
- 웹결제 signkey
- INILite Key: 정기결제 이용 시 필수로 입력해야 하는 값
- INIAPI KEY, INIAPI IV: 결제 취소 및 하위 상점 관련 API 입력 시 필수로 입력해야 하는 값
2. 포트원 관리자 콘솔에서 실연동 채널 추가하기
포트원 관리자 콘솔에 로그인하면 이동하는 메인 화면입니다.
여기서 왼쪽 메뉴칸에 있는 결제 연동 - 연동 정보를 클릭하면 아래 화면으로 이동하게 됩니다.
그런 다음 실연동과 테스트 중 원하는 탭을 선택한 뒤 채널 추가 버튼을 클릭하면 됩니다.
정기결제 기능의 경우 실연동과 테스트 채널의 차이는 카드 정보를 입력했을 때 실결제가 되는지 / 안되는지의 차이입니다.
실연동 채널로 테스트를 진행한 뒤 결제된 금액은 왼쪽의 요금 및 결제 메뉴에서 취소가 가능하지만 번거로울 수 있으니 결제 기능을 완벽하게 구현하기 전 까지는 테스트 채널을 사용하시는걸 추천드립니다.
채널 추가 버튼을 클릭하면 결제대행사와 결제 모듈을 선택해야 합니다.
포트원을 처음 사용하시는 분들은 결제 모듈에서 어떤 것을 선택해야 하는지 어려움을 느낄 수도 있는데, 아래 사진을 참고하시면 됩니다.
api는 이전에 설명한 것 처럼 결제창을 직접 구현했을 때를 말하기 때문에 rest api 방식으로 결제창을 구현하셨다면 해당하는 것을 골라서 선택하시면 됩니다.
본인인증은 포트원에서 제공하는 또 다른 api이기 때문에 해당 기능이 필요한 경우에만 선택하면 됩니다.
저는 sdk 결제창 방식과 V1 버전을 사용하기 때문에 두 번째에 있는 일반/정기결제 를 선택하여 진행했습니다.
그런 다음 채널 이름과 채널 속성을 선택하고 결제대행사 계약 시 메모해둔 PG상점아이디(MID), 웹결제 signkey, INILite Key, INIAPI KEY, INIAPI IV 값을 입력하고 저장하면 됩니다.
3. 포트원 SDK 설치하기
결제창 연동을 진행할 주문 페이지에 아래 js 라이브러리를 추가합니다.
<script src="https://cdn.iamport.kr/v1/iamport.js"></script>
4. IMP.request_pay() 함수 호출하여 빌링키 발급받기
먼저 IMP.request_pay() 함수를 호출하여 결제창을 열기 위해서는 포트원 sdk를 초기화해야 합니다.
declare const window: typeof globalThis & {
IMP: any
}
const { IMP } = window
IMP.init('고객사 식별코드')
타입스크립트를 사용했기 때문에 IMP에 대한 타입 선언을 먼저 해주었고 관리자 콘솔의 결제 연동 메뉴에서 연동 정보를 클릭 후 상단의 식별코드 / API Keys 탭을 클릭하면 고객사 식별코드를 확인할 수 있습니다.
포트원 sdk를 초기화하는 코드를 작성했다면 이제 IMP.request_pay() 함수를 호출하면 되는데
IMP.request_pay() 함수를 호출하려면 merchant_uid(상점 고유번호) 값을 필수 파라미터로 작성해야 하고, 이때 customer_uid 값을 파라미터에 추가하면 빌링키를 발급하는 결제창을 열 수 있습니다.
참고로 customer_uid 값은 PG사가 발급한 빌링키와 맵핑되는, 고객사가 지정한 고유한 값입니다.
또한 서버에서 포트원에 A 고객의 결제를 요청할 때, customer_uid값을 통해 A 고객이 등록해둔 카드 중 이와 매칭되는 카드 빌링키를 찾아 해당 정보로 결제를 진행하는 것입니다.
말이 좀 어려워서 이해하기 어려운 부분이 있는데 예를 들어 설명하자면
정부24 사이트에서 비대면 주민등록증 발급을 신청한 후, 발급된 주민등록증을 찾으러 동네의 행정복지센터에 방문하면 지문인식을 통해 본인임을 인증하고 발급된 주민등록증을 받아올 수 있는데 주민등록증과 지문인식을 각각 빌링키와 customer_uid 라고 생각하면 됩니다.
즉 서버에서는 고객의 customer_uid 값을 보관하고, 결제대행사에서는 그에 상응하는 빌링키를 보관하고 있다가 서버에서 customer_uid 값으로 결제를 요청하면 결제대행사에서 해당 빌링키를 찾아 결제를 승인하는 것을 말합니다.
그렇기 때문에 customer_uid 값과 merchant_uid 값을 프론트에서 랜덤으로 생성하기 보다는 서버에서 발급받는 것이 보안상 더 좋을 것이라 판단하여 사용자가 결제를 위해 '멤버십 이용하기' 라는 버튼을 클릭하면 서버에 post 요청을 보내 customer_uid 값과 merchant_uid 값을 받아오는 함수를 연결했고, 값을 받아오는 데 성공하면 결제창이 열릴 수 있도록 useEffect 를 사용해 IMP.request_pay() 함수를 작성했습니다.
useEffect(() => {
const type = membershipType === 'BASIC' ? 0 : 1
if (isSuccess && userInfo && memberships) {
IMP.request_pay(
{
channelKey: '관리자콘솔 결제 연동 - 연동 정보 - 상단의 상점아이디',
pay_method: 'card',
merchant_uid: merchantId, // 서버에서 발급, 필수 파라미터
name: memberships[type].name,
customer_uid: customerUid, // 서버에서 발급, 빌링키 발급을 위한 필수 파라미터
amount: memberships[type].price,
buyer_name: userInfo.name,
customer_id: userInfo.uuid,
notice_url: '웹훅 수신을 위한 url',
m_redirect_url: '모바일 리디렉션을 위한 url',
async (response: any) => {
// IMP.request_pay 함수 호출에 성공하면 서버에 필요한 값 전달
if (response.success) {
requestPayComplete({
imp_uid: response.imp_uid,
merchant_uid: merchantId,
status: response.status,
})
} else {
console.log(response.error_msg)
showSnackBar(response.error_msg, 'error')
}
},
)
}
}, [isSuccess])
여기서 필수 파라미터는 merchant_uid 뿐이고 빌링키 발급을 하려면 customer_uid 값을 추가하면 됩니다.
다른 파라미터는 선택적으로 추가하면 되는데, 아마도 결제대행사 심사 통과를 위해 buyer_name과 customer_id 값을 추가했던 것으로 기억합니다. 이 부분은 실제 서비스가 아니라면 신경쓰지 않아도 되고 실제 서비스인 경우 해당 결제대행사가 요청하는 파라미터를 추가하면 됩니다.
notice_url은 웹훅을 수신하기 위한 url 인데 이것도 서버에서 구현하는 것이기 때문에 설명만 남기겠습니다.
하지만 정기결제를 진행하기 위해서는 웹훅 생성이 필수라고 생각하시면 됩니다. m_redirect_url은 모바일 화면에서 결제를 진행할 때, 카드 정보를 입력하고 확인 버튼을 클릭하면 이동하는 페이지를 의미합니다. 지정해둔 m_redirect_url의 파라미터로 결제 성공 여부와 결제 실패시 실패 이유를 확인할 수 있습니다.
IMP.request_pay() 함수가 성공적으로 실행되면 열리는 결제창의 모습입니다.
여기서 카드 정보를 입력하고 공인인증서(pc에서만)를 통해 인증을 완료하면 빌링키가 발급되며 서버에 해당 결제 정보를 전달하면 서버에서 그 정보들을 가지고 결제대행사에 결제 요청을 보내게 되고 이 결제 요청이 성공적으로 이루어지면 실결제가 진행됩니다. 고객이 카드 정보를 입력한 후 확인 버튼을 클릭하면 위의 과정들이 진행된 후 결제가 완료되는 것입니다.
여기까지가 프론트엔드의 입장에서 진행해본 포트원 정기결제 구현 과정입니다.
혹여나 잘못된 부분이 있다면 댓글 남겨주세요!
궁금한 점에 대해 댓글 남겨주시면 아는 선에서 최대한 도움 드리도록 노력해보겠습니다.
'React' 카테고리의 다른 글
[React] Styled-Component 조건부 스타일링 적용하기 (0) | 2024.05.11 |
---|---|
React에 구글 폰트 적용하기 (0) | 2022.08.30 |
Props 의 특징 (0) | 2022.07.14 |
Components 와 Props 의 정의 (0) | 2022.07.13 |
Elements 란? (특징 및 렌더링하기) (0) | 2022.07.05 |