[스프링] 토스 간편결제 기능 구현하기

2024. 9. 10. 17:35·Backend/프로젝트
목차
  1. 0. 개요
  2. 1. 토스 개발자 센터에서 시크릿 키 발급받기
  3. 2. 발급받은 키를 이용하기 위해 yml설정을 추가하기
  4. 3.  인코딩한 값을 만들고 인터셉터를 통해 헤더에 인코딩한 값을 넣어주자!
  5. 4. 결제 요청 로그 남기기
  6. 5. Exception 발생시 예외처리
  7. 6. 지금까지 만든 인터셉터와 Bean 등록
  8. 7. OpenFegin를 통해 외부 API를 호출하기
  9. 5. 테스트 진행
반응형

0. 개요

프로젝트를 진행하면서 토스 간편 결제를 도입하기로 결정해서 토스 개발자 센터의 문서를 따라가보며 해당 기능을 구현해 보았습니다.

토스의 결제는 위의 그림처럼 진행되는데 서버의 역할인 결제승인 API를 호출하고 해당 응답을 처리하는 기능만 구현해보았습니다.

 

1. 토스 개발자 센터에서 시크릿 키 발급받기

토스 개발자센터에서 시크릿 키를 발급받습니다. (노출되지 않도록 조심!) 저는 실제 운영환경이 아닌 개인 프로젝트를 진행하기 때문에 테스트 키를 통해 진행해보도록 하겠습니다.

(테스트 키를 통해 일어난 결제는 이뤄지지 않고 가상으로 이뤄집니다.)

 

2. 발급받은 키를 이용하기 위해 yml설정을 추가하기

application.yml

payment:
  secret-key: 시크릿키 넣어줍시다
  base-url: https://api.tosspayments.com/v1/payments
  confirm-endpoint: /confirm

 

 

 

3.  인코딩한 값을 만들고 인터셉터를 통해 헤더에 인코딩한 값을 넣어주자!

토스페이먼츠 API에서는 HTTP 헤더에 사용자 시크릿 키를 base64로 인코딩한 값을 넣어줘야 합니다. 인코딩한 값을 만들기 위한 메서드를 만들어주고 인터셉터를 통해 요청을 보낼 때마다 헤더에 해당 키를 넣어줄 수 있도록 할 수 있습니다.

 

(이 때 주의할 점은 맨뒤에 콜론('  : ' )을 추가하는 것과 Basic 맨뒤에 한칸 띄어쓰기 한 후 인코딩 된 값을 붙여줘야 합니다.)

public class PaymentAuthInterceptor implements RequestInterceptor {
    private static final String AUTH_HEADER_PREFIX = "Basic ";

    private final PaymentProperties paymentProperties;

    public PaymentAuthInterceptor(final PaymentProperties paymentProperties) {
        this.paymentProperties = paymentProperties;
    }

    @Override
    public void apply(final RequestTemplate template) {
        final String authHeader = createPaymentAuthorizationHeader();
        template.header("Authorization", authHeader);
    }

    private String createPaymentAuthorizationHeader() {
        final byte[] encodedBytes = Base64.getEncoder().encode((paymentProperties.getSecretKey() + ":").getBytes(StandardCharsets.UTF_8));
        return AUTH_HEADER_PREFIX + new String(encodedBytes);
    }
}

 

4. 결제 요청 로그 남기기

결제 요청 로그를 인터셉터를 통해 남기도록 해줍니다.

public class PaymentLoggingInterceptor implements RequestInterceptor {

    private static final Logger logger = LoggerFactory.getLogger(PaymentLoggingInterceptor.class);

    @Override
    public void apply(RequestTemplate template) {
        logger.info("Payment Request: {} {}", template.method(), template.url());
        logger.info("Payment Request Body: {}", new String(template.body()));
    }
}

 

 

5. Exception 발생시 예외처리

토스에서는 예외가 발생하면 위 사진과 같은 에러 객체를 응답해주는데, 해당 응답에 맞춰 예외처리를 해줍니다.

 

6. 지금까지 만든 인터셉터와 Bean 등록

public class PaymentFeignConfig {

    private final PaymentProperties paymentProperties;

    public PaymentFeignConfig(PaymentProperties paymentProperties) {
        this.paymentProperties = paymentProperties;
    }

    @Bean
    public Request.Options requestOptions() {
        return new Request.Options(2, TimeUnit.SECONDS, 60, TimeUnit.SECONDS, true);
    }

    @Bean
    public PaymentErrorDecoder paymentErrorDecoder() {
        return new PaymentErrorDecoder();
    }

    @Bean
    PaymentAuthInterceptor paymentAuthInterceptor() {
        return new PaymentAuthInterceptor(paymentProperties);
    }

    @Bean
    PaymentLoggingInterceptor paymentLoggingInterceptor() {
        return new PaymentLoggingInterceptor();
    }
}

 

    @Bean
    public Request.Options requestOptions() {
        return new Request.Options(2, TimeUnit.SECONDS, 30, TimeUnit.SECONDS, true);
    }

추가로 외부 API가 문제 혹은 네트워크 문제로 인해 요청시간이 오래 걸리거나, 연결은 성공했지만 응답까지의 시간이 지연되는 경우 대기 상태에 빠지지 않도록 일정 시간이 지나면 요청을 취소하도록타임 아웃을 설정해줍니다.

  • Connection Timeout : 클라이언트에서 설정한 시간까지 서버에 연결되지 않으면 발생
  • Read Timeout: 클라이언트에서 서버가 연결은 됐지만 서버가 클라이언트의 요청을 정상적으로 처리하지 못할 경우 발생

 

7. OpenFegin를 통해 외부 API를 호출하기

@FeignClient(name = "paymentClient", url = "${spring.payment.base-url}", configuration = PaymentFeignConfig.class)
public interface PaymentClient {

    @PostMapping(value = "/confirm", consumes = MediaType.APPLICATION_JSON_VALUE)
    PaymentConfirmResponse confirmPayment(@RequestBody PaymentConfirmRequest paymentConfirmRequest);
    
    }

 

OpenFeign은 Spring Cloud에서 제공하는 선언형 REST 클라이언트로, 간단하게 인터페이스를 통해 외부 API를 호출할 수 있게 해줍니다. 위 코드에서 같이 @FeignClient를 사용하면 Spring은 PaymentClient 인터페이스를 구현하는 클래스를 자동으로 생성하고, 이를 통해 토스 서버에 결제 승인 요청을 보낼 수 있습니다.

 

PaymentService.java

public PaymentConfirmResponse confirmPayment(final PaymentConfirmRequest paymentConfirmRequest, final Long reservationId) {
	final PaymentConfirmResponse paymentConfirmResponse = paymentClient.confirmPayment(paymentConfirmRequest);
 	final Payment payment = paymentConfirmResponse.toPayment(reservationId);
    
 	paymentRepository.save(payment);
	return paymentConfirmResponse;
}

 

PaymentConfirmRequest.java

@Getter
@NoArgsConstructor
public class PaymentConfirmRequest {
    private String orderId;
    private int amount;
    private String paymentKey;

    public PaymentConfirmRequest(String orderId, int amount, String paymentKey) {
        this.orderId = orderId;
        this.amount = amount;
        this.paymentKey = paymentKey;
    }
}

 

PaymentController.java

@RestController
public class PaymentController {

    private final PaymentService paymentService;

    public PaymentController(final PaymentService paymentService) {
        this.paymentService = paymentService;
    }

    @PostMapping("/api/{reservationId}/payment")
    public ResponseEntity<PaymentConfirmResponse> confirm(@RequestBody final PaymentConfirmRequest paymentConfirmRequest, @PathVariable("reservationId") final Long reservationId) {
        final PaymentConfirmResponse paymentConfirmResponse =  paymentService.confirmPayment(paymentConfirmRequest, reservationId);
        return ResponseEntity.ok(paymentConfirmResponse);
    }
}

토스 간편 결제 성공시 아래와 같은 응답이 오게 되는데 각자 필요한 값들을 저장해주면 됩니다. 

 

5. 테스트 진행

 

테스트 진행시 성공적으로 결제되는 것을 볼 수 있습니다.

 

 

참고

https://developers.tosspayments.com/

반응형

'Backend > 프로젝트' 카테고리의 다른 글

[Spring] Redis 캐시 적용하기  (0) 2024.10.31
nGrinder Docker 설치 및 사용방법  (2) 2024.10.25
Redis 분산 락(Distribution Lock)으로 동시성 문제 해결하기(+ AOP)  (2) 2024.08.18
스프링에서 동시성 문제 해결하기(낙관적 락, 비관적 락)  (0) 2024.08.14
QueryDsl을 활용한 카테고리별 조회(페이징) 기능 만들기  (1) 2024.07.30
  1. 0. 개요
  2. 1. 토스 개발자 센터에서 시크릿 키 발급받기
  3. 2. 발급받은 키를 이용하기 위해 yml설정을 추가하기
  4. 3.  인코딩한 값을 만들고 인터셉터를 통해 헤더에 인코딩한 값을 넣어주자!
  5. 4. 결제 요청 로그 남기기
  6. 5. Exception 발생시 예외처리
  7. 6. 지금까지 만든 인터셉터와 Bean 등록
  8. 7. OpenFegin를 통해 외부 API를 호출하기
  9. 5. 테스트 진행
'Backend/프로젝트' 카테고리의 다른 글
  • [Spring] Redis 캐시 적용하기
  • nGrinder Docker 설치 및 사용방법
  • Redis 분산 락(Distribution Lock)으로 동시성 문제 해결하기(+ AOP)
  • 스프링에서 동시성 문제 해결하기(낙관적 락, 비관적 락)
여포개발자
여포개발자
여포개발자
어제보다 오늘 더
여포개발자
전체
오늘
어제
  • 분류 전체보기 (141)
    • Backend (41)
      • 프로젝트 (18)
      • MSA 전환 (10)
      • spring (6)
      • JPA (7)
    • JAVA (11)
    • Kotlin 정리 (11)
    • 알고리즘 (59)
      • 프로그래머스 LV0 (5)
      • 프로그래머스 LV1 (12)
      • 프로그래머스 LV2 (17)
      • 프로그래머스 LV3 (8)
      • 백준 (14)
      • 소프티어 (3)
    • 네트워크 (3)
    • Docker (3)
    • SQL (5)
    • Kafka (6)
    • 일상 (1)
    • .NET (0)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • 프로그래머스LV1
  • 프로젝트
  • JAVA #프로그래머스 #LV0
  • 백준
  • #JAVA #프로그래머스 #LV1 #모두화이팅
  • MSA
  • TroubleShooting #JPA
  • #프로그래머스 #자바
  • 오블완
  • 네트워크
  • #JAVA #프로그래머스
  • Kotlin
  • HTTP
  • 모니터링
  • docker #MySQL
  • #프로그래머스
  • 티스토리챌린지
  • Kotiln
  • #JPA #JAVA
  • #JAVA #프로그래머스 #LV1
  • 자바 #백준
  • java
  • docker
  • Spring
  • 프로그래머스
  • JPA

최근 댓글

최근 글

반응형
hELLO· Designed By정상우.v4.5.2
여포개발자
[스프링] 토스 간편결제 기능 구현하기
상단으로

티스토리툴바

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.