| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 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 |
- 스프링 배치
- redis
- 김영한
- 스프링
- 트러블슈팅
- 배치
- 프로그래머스
- 템플릿 메서드 패턴
- 빌더 패턴
- 프록시 패턴
- lv1
- Spring
- Til
- 추상클래스
- GoF 23
- Spring Batch
- java
- spring boot
- 로드밸런서
- 백엔드
- DB
- 자바
- 성능 개선
- 코드카타
- 토스
- Effective Java
- 디자인 패턴
- 계산기
- 이펙티브 자바
- 스케줄러
- Today
- Total
김코딩
토스 결제 승인부터 포인트 충전까지의 전체 플로우 본문
도입 배경
요청 body에 넣어주는 방식으로 포인트 충전을 진행하였다.
하지만 이번에 토스페이먼츠를 도입하면서 결제 요청부터 승인,
그리고 포인트 충전까지의 실제 플로우가 어떻게 동작하는지 살펴보자.
왜 토스 페이먼츠를 도입하였는가?
PG사 연동을 목표로 개발을 진행하면서 다양한 PG사(토스 페이먼츠, 카카오 페이, 네이버 페이)를 찾아보았다.
그중에서도 우리는 왜 토스 페이먼츠를 선택하였는지 살펴보자.
| 구분 | 토스페이먼츠 | 카카오페이 | 네이버페이 |
| API 문서 품질 | 매우 우수 | 우수 | 보통 |
| 연동 복잡도 | 낮음 | 높음 | 높음 |
| 결제 수단 | 카드 / 계좌 / 가상계좌 | 카드 / 계좌 | 카드 / 네이버페이 |
| 개발 편의성 | 높음 | 낮음 | 낮음 |
| 기술 지원 | 빠른 응답 | 느림 | 느림 |
| 테스트 결제 | 지원 | 지원 | 지원 |
최종 선택 이유:
- 명확하고 친화적인 개발자 문서
- 간단한 REST API 연동 방식
- 다양한 결제 수단 지원
- 빠른 기술 지원 대응
토스 공식 문서
https://docs.tosspayments.com/guides/v2/payment-widget/integration
연동하기 | 토스페이먼츠 개발자센터
토스페이먼츠의 간편한 결제 연동 과정을 한눈에 볼 수 있습니다. 각 단계별 설명과 함께 달라지는 UI와 코드를 확인해보세요.
docs.tosspayments.com
결제 흐름 이해하기 | 토스페이먼츠 개발자센터
카드 결제 과정의 세 가지 핵심 단계인 요청, 인증, 승인을 이해하고 결제 정보를 검증하는 방법을 알아보세요.
docs.tosspayments.com
토스페이먼츠 결제 플로우

1. 결제위젯 랜더링

1. 사용자는 주문서에 진입한다.

2. 프론트엔드(Client)는 토스페이먼츠에 결제 위젯 렌더를 요청한다.

3. 토스페이먼츠는 프론트엔드(Client)에 결제위젯을 렌더링 해주고, 구매자는 프론트엔드가 받아온 결제위젯 화면을 본다.
-> 결제 위젯 화면은 다음과 같고, 구매자는 해당 화면에서 결제수단을 선택하게 된다.

2. 결제 수단 선택 및 결제 요청

1. 구매자가 결제 수단을 선택하고 결제하기 버튼을 클릭한다.

2. 그 전에 프론트에서 해줘야 하는 작업이 있다. 결제 금액을 설정해주어야 한다. 우리의 프로젝트의 경우, 4가지의 포인트 카드가 보이는데, 카드를 선택하면 자동으로 금액을 설정해 준다.
// ------ 주문의 결제 금액 설정 ------
await widgets.setAmount({
currency: "KRW",
value: 50000,
});
3. 결제수단을 선택하고 결제하기 버튼을 누르면 프론트에서 토스페이먼츠에 결제 요청을 한다.
- 나는 아래와 같은 요청으로 결제 요청을 보냈다.
- 금액
- orderId(UUID로 생성한 고유한 아이디)
- orderName(상품 이름 예: 포인트 1000원)
- successUrl(토스 결제 성공 후 이동할 Url)
- failUrl(토스 결제 실패 후 이동할 Url) - 실패하는 경우 : 계좌에 돈이 부족한 경우, 한도 초과

4. 토스에서는 결제 금액 무결성을 위해 orderId와 amount를 백엔드 서버에 임시저장 할 것을 권장한다.

나는 해당 정보를 데이터베이스에 저장하여 관리를 할 예정이다.
3. 결제정보 입력 및 결제 시도

1. 전화번호 + 생년월일을 입력하거나, QR코드를 스캔하여 결제를 진행하면 된다.

2. 결제 성공 시 successUrl로 이동한다.
- 이때 다음과 같은 네 가지 정보가 쿼리 파라미터에 추가된다.

- 이때 토스는 amount값을 백엔드 서버에서 검증하는 것을 권고하고 있다.



4. 결제 정보 전달 및 결제 승인 요청

1. 백엔드 서버에 결제정보를 전달한다.
- 이는 결제 승인 요청을 대신해주기 위함인데 프론트엔드에서 토스페이먼츠로 바로 해주는 것이 아니라 백엔드를 한번 거친다는 것이 특징이다.
이유는 구체적으로 언급되어 있지 않지만 결제 승인 API를 보낼 때 시크릿키를 넣는데 이것이 프론트엔드에서 관리되면 유출된 위험이 높기 때문에 백엔드를 거치는 것 같다. - 이 때, 서버에는 다음과 같은 정보를 전달해야 한다고 한다.

2. 백엔드 서버는 토스페이먼츠 측으로 결제 승인 API 요청을 보낸다.
- 결제 승인 API에 대한 명세는 다음과 같다.
paymentKey, orderId, amount가 필수적으로 들어가야 한다고 한다. 이것들은 바로 프론트 서버에서 받아온 정보들이다. - 이 정보들은 successUrl에서 파라미터로 넘어온 값들을 결제 승인 API 요청body로 사용한다.

https://docs.tosspayments.com/reference#%EA%B2%B0%EC%A0%9C-%EC%8A%B9%EC%9D%B8
코어 API | 토스페이먼츠 개발자센터
토스페이먼츠 API 엔드포인트(Endpoint)와 객체 정보, 파라미터, 요청 및 응답 예제를 살펴보세요.
docs.tosspayments.com
- 승인 성공시 payment라는 객체를 받아오는데 다양한 필드들이 존재한다. 여기서 서비스 요구사항에 맞는 필드들을 선정해 데이터베이스에 저장하면 될 것 같다.

5. 결제 승인결과 전달

1. 결제 승인 결과를 백엔드에서 프론트에게 제공한다.
2. 최종 결과를 구매자에게 제공한다.

Confirm API 내에서 포인트를 저장하는 로직 이해하기
지금까지는 기본적인 Toss Payments의 결제 플로우를 알아보았다.
지금부터는 결제 승인이 이루어질때, 포인트가 어떻게 충전이 되는지 알아보겠다.
결제 준비 API
- 위에서 토스는 결제를 진행하기 전에, orderId와 amount를 서버에서 관리하는 것을 권하고 있다. 나는 결제 진행 전에 /api/payments/prepare API를 호출하여, 데이터베이스에 orderId와 amount를 저장한다.
/**
* 토스 결제 요청 전, orderId와 amount를 데이터베이스에 저장하기 위한 API
* @param auth 인증된 사용자 정보
* @param request amount(요청 가격)
* @return amount(요청 가격), orderId(UUID로 생성된 고유 ID), orderName(주문한 상품 이름 예: 포인트 10,000원)
*
* 토스에서는 결제 요청 전 orderId와 amount를 세션이나 데이터베이스에 저장하는 것을 적극 권장한다.
* @see <a href="https://docs.tosspayments.com/guides/v2/get-started/payment-flow#%EB%8D%94-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0">토스페이먼츠 가이드</a>
*/
@PostMapping("/payments/prepare")
public ResponseEntity<ApiResponse<PaymentPrepareResponse>> preparePayment(
@AuthenticationPrincipal Auth auth,
@Valid @RequestBody PaymentPrepareRequest request
) {
PaymentPrepareResult result = paymentFacade.preparePayment(auth.getId(),
PaymentPresentationMapper.toPaymentPrepareCommand(request));
return ApiResponse.success(
HttpStatus.CREATED,
"결제 준비가 완료되었습니다.",
PaymentPresentationMapper.toPaymentPrepareResponse(result)
);
}
결제 승인 API
- successUrl에서 호출하는 우리 프로젝트의 API이다. 여기에서 토스의 결제 승인 API 즉, /payments/confirm API를 호출하게 된다.
@Operation(summary = "결제 승인", description = "토스 결제 성공 후, 토스 페이먼츠에 승인 요청을 보내고, 포인트를 충전합니다.")
@ApiResponses({
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "결제가 완료되었습니다. 포인트는 1~2분 이내로 충전될 예정입니다."),
})
@PostMapping("/payments/confirm")
public ResponseEntity<ApiResponse<PaymentConfirmResponse>> confirmAndChargePoint(
@Valid @RequestBody PaymentConfirmRequest request,
@Parameter(hidden = true) @AuthenticationPrincipal Auth auth) {
return ApiResponse.success(
HttpStatus.OK,
"결제가 완료되었습니다.",
paymentService.confirmAndChargePoint(auth.getId(), request)
);
}
결제 승인 및 포인트 충전 처리
/**
* 결제 승인 및 포인트 충전
*/
// 메인 메서드: 트랜잭션 외부
@Override
public PaymentConfirmResponse confirmAndChargePoint(Long userId, PaymentConfirmRequest request) {
userService.getProfile(userId);
// 토스 API 호출 (실패 시 그냥 예외 전파)
TossConfirmResult tossConfirmResult = tossClient.confirmPayment(
request.getPaymentKey(), request.getOrderId(), request.getAmount());
try {
// DB 작업만 try-catch 처리
return processPaymentSuccess(request, tossConfirmResult);
} catch (Exception e) {
// DB 작업 실패 시에만 보상 트랜잭션
paymentCompensateService.executePaymentCompensation(
tossConfirmResult.getPaymentKey(),
"시스템 오류로 인한 자동 취소"
);
throw e;
}
}
// DB 작업만 짧은 트랜잭션
@Transactional
protected PaymentConfirmResponse processPaymentSuccess(PaymentConfirmRequest request,
TossConfirmResult tossConfirmResult) {
// 결제 성공 처리
Payment payment = markAsSuccess(tossConfirmResult, request.getAmount());
// 포인트 충전
pointService.chargePoint(
payment.getUserId(),
payment.getAmount().getValue(),
"CHARGE",
payment.getOrderId().getValue()
);
return PaymentConfirmResponse.toDto(payment);
}
1. PaymentService.confirmAndChargePoint() 호출
- 사용자 검증
- 토스 결제 승인 API 호출
2. Payment 엔티티 성공 응답 업데이트
- 토스 응답 데이터로 prepare API로 저장한 Payment 객체를 성공 응답으로 업데이트
3. 토스 결제 승인 성공 후 후처리 실패 시(결제 DB 성공처리 or 포인트 충전)
- 자동으로 토스 결제 취소 API를 호출하여 데이터 정합성 유지
플로우 차트

'TIL' 카테고리의 다른 글
| Naver Green Developer 후기 - FINSight (0) | 2025.12.01 |
|---|---|
| 동기식 처리에서 비동기식 처리로 장애 격리 달성 테스트 (3) | 2025.08.07 |
| 비동기식 결제 승인 API의 응답속도 테스트 및 분석 (0) | 2025.08.06 |
| 동기식 결제 승인 API의 응답속도 테스트 및 분석 (1) | 2025.08.06 |
| 트러블슈팅 - 모든 에러가 500에러? 원인은 DTO 였다.. (0) | 2025.05.23 |