Skip to content

Commit

Permalink
fix: 토스 결제 승인 실패 시 예외 처리 (#188)
Browse files Browse the repository at this point in the history
* fix: 토스 결제 승인 실패 시 예외 throw

* test: 결제 승인 로직 변경에 따른 테스트 수정
  • Loading branch information
kmebin authored Nov 29, 2023
1 parent bd1c3b6 commit 645dd14
Show file tree
Hide file tree
Showing 12 changed files with 118 additions and 145 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@
import com.moabam.api.dto.payment.ConfirmPaymentRequest;
import com.moabam.api.dto.payment.ConfirmTossPaymentResponse;
import com.moabam.api.dto.payment.PaymentRequest;
import com.moabam.api.infrastructure.payment.TossPaymentMapper;
import com.moabam.api.infrastructure.payment.TossPaymentService;
import com.moabam.global.error.exception.MoabamException;
import com.moabam.global.error.exception.NotFoundException;

import lombok.RequiredArgsConstructor;
Expand All @@ -38,24 +36,26 @@ public void request(Long memberId, Long paymentId, PaymentRequest request) {
payment.request(request.orderId());
}

@Transactional
public void confirm(Long memberId, ConfirmPaymentRequest request) {
public Payment validateInfo(Long memberId, ConfirmPaymentRequest request) {
Payment payment = getByOrderId(request.orderId());
payment.validateInfo(memberId, request.amount());

try {
ConfirmTossPaymentResponse response = tossPaymentService.confirm(
TossPaymentMapper.toConfirmRequest(request.paymentKey(), request.orderId(), request.amount())
);
payment.confirm(response.paymentKey(), response.approvedAt());
return payment;
}

@Transactional
public void confirm(Long memberId, Payment payment, ConfirmTossPaymentResponse response) {
payment.confirm(response.paymentKey());

if (payment.isCouponApplied()) {
couponService.discount(payment.getCouponWalletId(), memberId);
}
bugService.charge(memberId, payment.getProduct());
} catch (MoabamException exception) {
payment.fail(request.paymentKey());
if (payment.isCouponApplied()) {
couponService.discount(payment.getCouponWalletId(), memberId);
}
bugService.charge(memberId, payment.getProduct());
}

@Transactional
public void fail(Payment payment, String paymentKey) {
payment.fail(paymentKey);
}

private Payment getById(Long paymentId) {
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/com/moabam/api/domain/payment/Payment.java
Original file line number Diff line number Diff line change
Expand Up @@ -133,9 +133,9 @@ public void request(String orderId) {
this.requestedAt = LocalDateTime.now();
}

public void confirm(String paymentKey, LocalDateTime approvedAt) {
public void confirm(String paymentKey) {
this.paymentKey = paymentKey;
this.approvedAt = approvedAt;
this.approvedAt = LocalDateTime.now();
this.status = PaymentStatus.DONE;
}

Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,22 @@
import org.springframework.http.HttpStatusCode;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.WebClient;

import com.moabam.api.dto.payment.ConfirmTossPaymentRequest;
import com.moabam.api.dto.payment.ConfirmPaymentRequest;
import com.moabam.api.dto.payment.ConfirmTossPaymentResponse;
import com.moabam.global.config.TossPaymentConfig;
import com.moabam.global.error.exception.MoabamException;
import com.moabam.global.error.exception.TossPaymentException;
import com.moabam.global.error.model.ErrorResponse;

import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import reactor.core.publisher.Mono;

@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class TossPaymentService {

Expand All @@ -37,13 +39,14 @@ public void init() {
.build();
}

public ConfirmTossPaymentResponse confirm(ConfirmTossPaymentRequest request) {
@Transactional
public ConfirmTossPaymentResponse confirm(ConfirmPaymentRequest request) {
return webClient.post()
.uri("/v1/payments/confirm")
.body(BodyInserters.fromValue(request))
.retrieve()
.onStatus(HttpStatusCode::isError, response -> response.bodyToMono(ErrorResponse.class)
.flatMap(error -> Mono.error(new MoabamException(error.message()))))
.flatMap(error -> Mono.error(new TossPaymentException(error.message()))))
.bodyToMono(ConfirmTossPaymentResponse.class)
.block();
}
Expand Down
15 changes: 14 additions & 1 deletion src/main/java/com/moabam/api/presentation/PaymentController.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,14 @@
import org.springframework.web.bind.annotation.RestController;

import com.moabam.api.application.payment.PaymentService;
import com.moabam.api.domain.payment.Payment;
import com.moabam.api.dto.payment.ConfirmPaymentRequest;
import com.moabam.api.dto.payment.ConfirmTossPaymentResponse;
import com.moabam.api.dto.payment.PaymentRequest;
import com.moabam.api.infrastructure.payment.TossPaymentService;
import com.moabam.global.auth.annotation.Auth;
import com.moabam.global.auth.model.AuthMember;
import com.moabam.global.error.exception.TossPaymentException;

import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
Expand All @@ -23,6 +27,7 @@
public class PaymentController {

private final PaymentService paymentService;
private final TossPaymentService tossPaymentService;

@PostMapping("/{paymentId}")
@ResponseStatus(HttpStatus.OK)
Expand All @@ -34,6 +39,14 @@ public void request(@Auth AuthMember member, @PathVariable Long paymentId,
@PostMapping("/confirm")
@ResponseStatus(HttpStatus.OK)
public void confirm(@Auth AuthMember member, @Valid @RequestBody ConfirmPaymentRequest request) {
paymentService.confirm(member.id(), request);
Payment payment = paymentService.validateInfo(member.id(), request);

try {
ConfirmTossPaymentResponse response = tossPaymentService.confirm(request);
paymentService.confirm(member.id(), payment, response);
} catch (TossPaymentException exception) {
paymentService.fail(payment, request.paymentKey());
throw exception;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.moabam.global.error.exception;

public class TossPaymentException extends MoabamException {

public TossPaymentException(String errorMessage) {
super(errorMessage);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import com.moabam.global.error.exception.ForbiddenException;
import com.moabam.global.error.exception.MoabamException;
import com.moabam.global.error.exception.NotFoundException;
import com.moabam.global.error.exception.TossPaymentException;
import com.moabam.global.error.exception.UnauthorizedException;
import com.moabam.global.error.model.ErrorResponse;

Expand Down Expand Up @@ -58,7 +59,7 @@ protected ErrorResponse handleBadRequestException(MoabamException moabamExceptio
}

@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler(FcmException.class)
@ExceptionHandler({FcmException.class, TossPaymentException.class})
protected ErrorResponse handleFcmException(MoabamException moabamException) {
return new ErrorResponse(moabamException.getMessage(), null);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
import java.util.Optional;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
Expand All @@ -19,13 +18,11 @@
import com.moabam.api.application.bug.BugService;
import com.moabam.api.application.coupon.CouponService;
import com.moabam.api.domain.payment.Payment;
import com.moabam.api.domain.payment.PaymentStatus;
import com.moabam.api.domain.payment.repository.PaymentRepository;
import com.moabam.api.domain.payment.repository.PaymentSearchRepository;
import com.moabam.api.dto.payment.ConfirmPaymentRequest;
import com.moabam.api.dto.payment.ConfirmTossPaymentResponse;
import com.moabam.api.dto.payment.PaymentRequest;
import com.moabam.api.infrastructure.payment.TossPaymentService;
import com.moabam.global.error.exception.MoabamException;
import com.moabam.global.error.exception.NotFoundException;

@ExtendWith(MockitoExtension.class)
Expand All @@ -40,88 +37,56 @@ class PaymentServiceTest {
@Mock
CouponService couponService;

@Mock
TossPaymentService tossPaymentService;

@Mock
PaymentRepository paymentRepository;

@Mock
PaymentSearchRepository paymentSearchRepository;

@DisplayName("결제를 요청한다.")
@Nested
class Request {

@DisplayName("해당 결제 정보가 존재하지 않으면 예외가 발생한다.")
@Test
void not_found_exception() {
// given
Long memberId = 1L;
Long paymentId = 1L;
PaymentRequest request = new PaymentRequest(ORDER_ID);
given(paymentRepository.findById(paymentId)).willReturn(Optional.empty());

// when, then
assertThatThrownBy(() -> paymentService.request(memberId, paymentId, request))
.isInstanceOf(NotFoundException.class)
.hasMessage("존재하지 않는 결제 정보입니다.");
}
@DisplayName("결제 요청 시 해당 결제 정보가 존재하지 않으면 예외가 발생한다.")
@Test
void request_not_found_exception() {
// given
Long memberId = 1L;
Long paymentId = 1L;
PaymentRequest request = new PaymentRequest(ORDER_ID);
given(paymentRepository.findById(paymentId)).willReturn(Optional.empty());

// when, then
assertThatThrownBy(() -> paymentService.request(memberId, paymentId, request))
.isInstanceOf(NotFoundException.class)
.hasMessage("존재하지 않는 결제 정보입니다.");
}

@DisplayName("결제 정보 검증 시 해당 결제 정보가 존재하지 않으면 예외가 발생한다.")
@Test
void validate_info_not_found_exception() {
// given
Long memberId = 1L;
Payment payment = payment(bugProduct());
ConfirmPaymentRequest request = confirmPaymentRequest();
given(paymentSearchRepository.findByOrderId(request.orderId())).willReturn(Optional.empty());

// when, then
assertThatThrownBy(() -> paymentService.validateInfo(memberId, request))
.isInstanceOf(NotFoundException.class)
.hasMessage("존재하지 않는 결제 정보입니다.");
}

@DisplayName("결제를 승인한다.")
@Nested
class Confirm {

@DisplayName("해당 결제 정보가 존재하지 않으면 예외가 발생한다.")
@Test
void not_found_exception() {
// given
Long memberId = 1L;
ConfirmPaymentRequest request = confirmPaymentRequest();
given(paymentSearchRepository.findByOrderId(request.orderId())).willReturn(Optional.empty());

// when, then
assertThatThrownBy(() -> paymentService.confirm(memberId, request))
.isInstanceOf(NotFoundException.class)
.hasMessage("존재하지 않는 결제 정보입니다.");
}

@DisplayName("쿠폰을 적용한 경우 쿠폰을 차감한 후 벌레를 충전한다.")
@Test
void use_coupon_success() {
// given
Long memberId = 1L;
Long couponWalletId = 1L;
Payment payment = paymentWithCoupon(bugProduct(), discount1000Coupon(), couponWalletId);
ConfirmPaymentRequest request = confirmPaymentRequest();
given(paymentSearchRepository.findByOrderId(request.orderId())).willReturn(Optional.of(payment));
given(tossPaymentService.confirm(confirmTossPaymentRequest())).willReturn(confirmTossPaymentResponse());

// when
paymentService.confirm(memberId, request);

// then
verify(couponService, times(1)).discount(couponWalletId, memberId);
verify(bugService, times(1)).charge(memberId, payment.getProduct());
}

@DisplayName("실패한다.")
@Test
void fail() {
// given
Long memberId = 1L;
Long couponWalletId = 1L;
Payment payment = paymentWithCoupon(bugProduct(), discount1000Coupon(), couponWalletId);
ConfirmPaymentRequest request = confirmPaymentRequest();
given(paymentSearchRepository.findByOrderId(request.orderId())).willReturn(Optional.of(payment));
given(tossPaymentService.confirm(any())).willThrow(MoabamException.class);

// when
paymentService.confirm(memberId, request);

// then
assertThat(payment.getStatus()).isEqualTo(PaymentStatus.ABORTED);
}
@DisplayName("결제 승인 시 쿠폰을 적용한 경우 쿠폰을 차감한 후 벌레를 충전한다.")
@Test
void confirm_with_coupon_success() {
// given
Long memberId = 1L;
Long couponWalletId = 1L;
Payment payment = paymentWithCoupon(bugProduct(), discount1000Coupon(), couponWalletId);
ConfirmTossPaymentResponse response = confirmTossPaymentResponse();

// when
paymentService.confirm(memberId, payment, response);

// then
verify(couponService, times(1)).discount(couponWalletId, memberId);
verify(bugService, times(1)).charge(memberId, payment.getProduct());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import org.springframework.test.context.ActiveProfiles;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.moabam.api.dto.payment.ConfirmTossPaymentRequest;
import com.moabam.api.dto.payment.ConfirmPaymentRequest;
import com.moabam.api.dto.payment.ConfirmTossPaymentResponse;
import com.moabam.global.config.TossPaymentConfig;
import com.moabam.global.error.exception.MoabamException;
Expand Down Expand Up @@ -58,7 +58,7 @@ class Confirm {
@Test
void success() throws Exception {
// given
ConfirmTossPaymentRequest request = confirmTossPaymentRequest();
ConfirmPaymentRequest request = confirmPaymentRequest();
ConfirmTossPaymentResponse expected = confirmTossPaymentResponse();
mockWebServer.enqueue(new MockResponse()
.setResponseCode(200)
Expand All @@ -76,7 +76,7 @@ void success() throws Exception {
@Test
void exception() {
// given
ConfirmTossPaymentRequest request = confirmTossPaymentRequest();
ConfirmPaymentRequest request = confirmPaymentRequest();
String jsonString = "{\"code\":\"NOT_FOUND_PAYMENT\",\"message\":\"존재하지 않는 결제 입니다.\"}";
mockWebServer.enqueue(new MockResponse()
.setResponseCode(404)
Expand Down
Loading

0 comments on commit 645dd14

Please sign in to comment.