티스토리 뷰
프로그래밍/JAVA
[kotlin/java/spring] 토스 페이먼츠 자동결제 (billing) 연동 방법 - 1. Billing Key 발급
rlawlstjd007 2022. 10. 19. 21:53Billing 연동
- 토스 페이먼츠를 사용하여 카드 자동결제 (Billing) 연동을 해보겠습니다.
- 본 글에서는 카드정보를 입력 받아 정보에 해당하는 Billing Key를 발급 받는 과정까지를 진행해봅니다.
Billing 이란?
- 최근 구독 형태의 서비스를 하는 서비스들은 유저에게 카드 정보를 입력을 받아 정기적으로 (Monthly) 결제를 수행하는 방식을 이용하고 있습니다.
- 보통 일반 P.G의 결제 기능을 사용하게 되면 매번 결제 시점에 유저가 인증을 해야 하는 방식입니다.
- 이때 사용할 수 있는 방식 중 하나가 P.G 사에서 제공하는 정기결제(Billing) 기능을 사용하여 구현할 수 있습니다.
- 카드 등록 시점 이후에는 P.G 사에서 제공하는 Billing Key 를 통해서 결제 요청을 할 수 있는 방식
Toss Payments
- 토스 페이먼츠에서 제공하는 Billing 서비스의 연동 과정은 아래와 같습니다.
- 실제 연동 문서는 여기 에서 확인
- 여기서 눈여겨 볼 점들은 결제창 구현 방식 입니다.
결제창
- 실제 유저에게 카드 정보를 입력받는 결제 창으로 보통 두가지 방식으로 구현할 수 있습니다.
- 각 방법에는 장단점이 존재합니다.
- 참고로 이번 예제에서는 P.G 결제창을 연동합니다.
P.G 제공 결제창
- P.G 사에서 제공하는 결제창을 그대로 사용 (Javascript 등 제공되는 SDK 사용)
- P.G사 결제창을 사용하는 경우에는 P.G사에서 제공하는 기능을 그대로 사용하면 되기 때문에 비교적 연동이 쉽다는 장점이 있지만 P.G사 결제창의 구현 방식에 의존적이라는 단점이 있습니다.
- 토스 페이먼츠의 경우 Iframe 방식으로 결제창을 로드하는데 만약 Product 의 구현 방식에 따라 (iframe 이 아닌 popup으로 띄워야 된다거나, 결제 과정중 추가적인 처리가 필요하다거나) 제약 사항이 생길 수 있습니다.
- 토스 페이먼츠의 경우 본인인증을 위해서 '휴대폰 인증'을 수행하는데 이 또한 유저의 이탈을 발생시킬 수 있기 때문에 고려해볼 점이긴 합니다.
Custom 결제창
- P.G 사에서 제공하는 API를 연동하여 직접 구현한 Custom 결제창 사용
- 결제창을 직접 구현하는 경우 결제창 자체를 구현하는데 추가적인 리소스가 들지만 직접 구현하는 만큼 구현방식에 대한 자유도를 얻을 수 있습니다. (ex. 토스 결제창의 약관 동의 Step을 생략한다던지 .. )
- 토스 페이먼츠 연동 문서에는 API 연동 방식에 대한 내용이 없는 것으로 보여 직접 문의를 해봐야 합니다.
설계
Sequence Diagram
- 아래는 추후 구현할 Application의 Flow의 시퀀스 다이어그램 형태입니다.
- 위 Toss Payments의 Flow를 실제 구현 스펙에 맞게 구체적으로 옮겨본 것으로 보시면 됩니다.
- 각 단계별로 자세히 보겠습니다.
- 카드 등록 (버튼 클릭)
- 유저가 카드 등록을 하는 순간 (예제에서는 간단한 버튼클릭) JavaScript 코드를 통해서 Toss 결제창 라이브러리를 호출
- 이때 Toss Client Key, Success/Fail Redirect URL 을 설정
- 결제창 호출
- Toss 라이브러리를 통해서 결제창이 호출되고 Iframe 형태로 결제창이 Display
- 카드 정보 입력
- 유저가 카드 정보 입력을 완료
- 카드 입력 결과 redirect (SUCCESS/FAIL)
- 결제창으로 입력된 카드 정보가 유효한지를 판단한 뒤 1번에서 설정한 Success/Fail URL로 redirect
- SUCCESS/FAIL 처리
- (SUCCESS인 경우) 빌링키 발급 요청
- 성공인 경우 빌링키 발급을 위한 authKey와 요청에 대한 식별자인 customerKey가 전달
- 해당 정보를 포함해서 Toss API 로 빌링키 발급 요청을 보냄
- (FAIL인 경우) 실패 화면 응답
- 실패인 경우 (유효하지 않은 카드 정보이거나) 실패 코드, 실패 사유가 전달된다.
- 예제에서는 실패 코드, 사유를 담아 실패 View를 리턴
- (SUCCESS인 경우) 빌링키 발급 요청
- 빌링키 응답
- 성공인 경우 빌링키를 응답받음. (실패인 경우 5-1 과 동일)
- 성공 화면 응답
- 발급된 빌링키를 담아 성공 View를 내려준다.
구현
- 위 설계를 토대로 실제 구현을 해보겠습니다.
- 구현 환경은 아래와 같습니다.
- Back-end: Kotlin Spring Boot
- Front-end: Thymleaf (편의를 위해서 Template Engine 을 사용)
소스코드
- 예제 전체 소스코드는 아래 위치에서 확인해보실 수 있습니다.
클라이언트, 시크릿 키 확인 방법
- 이후 예제에서 쓰일 클라이언트 키, 시크릿 키를 확인할 수 있는 방법입니다.
- 토스 페이먼츠 로그인 -> 개발 연동 -> 테스트 탭에서 키 값 확인
메인 화면
- 버튼 하나 존재하는 아주 심플한 HTML 코드입니다.
- 버튼 클릭시에 tossPayment 라이브러리를 호출하여 결제창을 Display 합니다. (Success/Fail URL 설정)
- 설정 값에 대한 자세한 내용은 이 곳 에서 확인
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8" />
<title>토스 페이먼츠 결제 연동 예제</title>
<script src="https://js.tosspayments.com/v1"></script>
</head>
<body>
<button id="card_button">카드 등록</button>
<script type="text/javascript" th:inline="javascript">
window.addEventListener('load', (event) => {
document.getElementById("card_button").addEventListener("click", function() {
var clientKey = "발급받은 클라이언트 키 (위에서 확인 방법 확인)";
var tossPayments = TossPayments(clientKey);
tossPayments.requestBillingAuth("카드", {
customerKey: "customer_key",
successUrl: window.location.origin + "/success",
failUrl: window.location.origin + "/fail"
});
});
});
</script>
</body>
</html>
Controller
- 카드 정보 입력 후 결제창으로 부터 redirect 되는 API 요청을 처리하기 위한 Controller 를 구현해보겠습니다.
- 각각 주석에 대해서 자세하게 살펴보겠습니다.
@Controller
class BillingController {
private val objectMapper = ObjectMapper()
@RequestMapping("/success")
fun success(
/**
* 1. Request Parameter 정보
*
* - authKey: 빌링키를 얻을 때 사용하는 인증 키
* - customerKey: 가맹점에서 사용하는 사용자별 고유 ID (내부에서 사용하는 카드 소유주별 식별자)
*/
@RequestParam requestParams: Map<String, String>,
model: Model,
): String {
/**
* 2. 발급 받은 시크릿 키 Base64 인코딩
*/
val encodedAuthHeader = Base64.getEncoder().encodeToString(("$SECRET_KEY:").toByteArray())
/**
* 3. 빌링키 발급 Reequest 생성
*/
val request: HttpRequest = HttpRequest.newBuilder()
.uri(URI.create("https://api.tosspayments.com/v1/billing/authorizations/${requestParams.getValue("authKey")}"))
.header("Authorization", "Basic $encodedAuthHeader")
.header("Content-Type", "application/json")
.method("POST", HttpRequest.BodyPublishers.ofString("{\"customerKey\":\"${requestParams.getValue("customerKey")}\"}"))
.build()
val response: HttpResponse<String> =
HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString())
return if (response.statusCode() == OK.value()) {
/**
* 4. 빌링키, 카드 정보 포함된 Json 으로 성공 View 리턴
*/
val jsonNode = objectMapper.readTree(response.body())
model.addAttribute("response", jsonNode.toPrettyString())
"success"
} else {
/**
* 5. 실패 View 리턴
*/
model.addAttribute("message", "카드 정보를 저장하는데 실패하였습니다.")
"fail"
}
}
@RequestMapping(value = ["/fail"])
fun billingFail(
/**
* 1. Request Parameter 정보
*
* - code: 실패 코드
* - customerKey: 실패 사유
*/
@RequestParam(required = false) code: String?,
@RequestParam(required = false) message: String?,
model: Model,
): String {
model.addAttribute("code", code)
model.addAttribute("message", message)
return "fail";
}
companion object {
const val SECRET_KEY = "발급 받은 시크릿 키 (발급 방법 위에서 확인)"
}
}
- Autentication 성공 Request Param 정보
- 카드 인증이 성공하여 redirect 되는 경우 authKey, customerKey 가 파라미터로 전달된다.
- 자세한 내용은 이 곳을 참고
- customerKey는 해당 카드에 대해서 내부에서 관리하는 식별자 정보로 인증의 용도로 사용된다고 이해하면 됨
- 보안 강화를 위해서 일회성 토큰을 사용해서 인증 단계를 추가로 진행하기도 하는 데 이건 나중에 기회가 되면 .. 다뤄보겠습니다.
- 카드 인증이 성공하여 redirect 되는 경우 authKey, customerKey 가 파라미터로 전달된다.
- 시크릿 키 인코딩
- Toss Payments 에서는 Basic Authorization 인증방식을 사용해서 발급 받은 시크릿키를
시크릿키:
형식으로 Base64 인코딩 하여 요청 헤더에 넣어줘야 합니다. - 인증에 대한 자세한 내용은 이 곳을 참고
- Toss Payments 에서는 Basic Authorization 인증방식을 사용해서 발급 받은 시크릿키를
- 빌링키 발급 Request 생성
- 빌링키 요청 포맷에 맞춰 authKey, customerKey 를 포함하여 요청 객체를 생성합니다.
- 빌링키 발급 요청에 대한 내용은 이 곳을 참고
- 빌링키 발급 성공 처리
- 발급에 성공한 경우 빌링키, 카드 정보를 포함한 정보가 리턴됩니다.
- 자세한 응답 값에 대한 내용은 이 곳을 참고
- 빌링키 발급 실패 처리
- Autentication 실패 Request Param 정보
- 카드 인증이 실패한 경우 실패 코드, 사유 정보와 함께 redirect 됩니다.
- 실패에 대한 스펙은 연동 문서에 없는 것 같아 보이는 군요 .. 흠.
Success/Fail
- 자, 이제 마지막 단계입니다.
- 성공, 실패에 대한 View 파일인데 이 또한 아주 심플한 모습입니다.
success.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>등록 성공 안내</title>
</head>
<body>
<h1>카드 등록에 성공하였습니다.</h1>
<div>
<label>카드 정보: </label>
<label th:text="${response}"/>
</div>
</body>
</html>
fail.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>등록 실패 안내</title>
</head>
<body>
<h1>카드 등록에 실패하였습나다.</h1>
<div>
<label>실패 코드: </label>
<label th:text="${code}"/>
</div>
<div>
<label>실패 사유: </label>
<label th:text="${message}"/>
</div>
</body>
</html>
- 본 글의 소스코드는 Toss Payment 결제창 연동, 빌링키 API 연동 방식에 대한 Core 소스를 이해하는데 초점을 맞추고 있어 실제 실행을 위해서는 아래 프로젝트 전체 소스코드로 실행을 해보는 걸 권장합니다.
Demo
Next...
- 다음 글에서는 발급 받은 빌링키를 가지고 실제 결제 요청을 하는 예제를 진행해보겠습니다.
반응형
'프로그래밍 > JAVA' 카테고리의 다른 글
[Java] String '+'문자열 연결 연산은 내부에서 어떻게 이루어질까? (0) | 2021.02.18 |
---|---|
[Java] String 객체는 어떻게 저장될까? (0) | 2021.01.22 |
[Java] System 환경 변수 가져오는 방법 (0) | 2020.11.09 |
[Java] Application Uncaught Exception 기본 핸들러 설정 방법 (0) | 2020.11.07 |
[Java] String <-> ZonedDate Time 변환 방법 (0) | 2020.11.06 |
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday
TAG
- 방통대 과제물
- JavaFX 테이블뷰
- effectivejava
- intelij
- 배낭 여행
- 텐트
- 일본 배낭여행
- JavaFX Window Close
- 이펙티브자바
- effective java
- 일본여행
- 일본 여행
- git
- 이펙티브 자바
- 스프링부트
- JavaFX 종료
- Java UI
- springboot
- JavaFX Table View
- 자바
- 이펙티브
- 인텔리제이
- 배낭여행
- 자전거
- 자전거 여행
- windows
- 일본 자전거 여행
- java
- JavaFX
- TableView
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
글 보관함