Skip to content

Commit

Permalink
fix: 토스 결제 승인 성공/실패 시 결과 반영 안되는 이슈 해결 (#194)
Browse files Browse the repository at this point in the history
* fix: 결제 정보 검증 및 토스 결제 승인 API 로직 트랜잭션 분리

* test: 로직 변경에 따른 테스트 수정
  • Loading branch information
kmebin authored Nov 30, 2023
1 parent 49b12fb commit 5f07beb
Show file tree
Hide file tree
Showing 7 changed files with 83 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
import com.moabam.api.domain.payment.Order;
import com.moabam.api.domain.payment.Payment;
import com.moabam.api.domain.product.Product;
import com.moabam.api.dto.payment.ConfirmTossPaymentResponse;
import com.moabam.api.dto.payment.PaymentResponse;
import com.moabam.api.dto.payment.RequestConfirmPaymentResponse;

import lombok.AccessLevel;
import lombok.NoArgsConstructor;
Expand Down Expand Up @@ -36,4 +38,12 @@ public static PaymentResponse toPaymentResponse(Payment payment) {
.build())
.orElse(null);
}

public static RequestConfirmPaymentResponse toRequestConfirmPaymentResponse(Payment payment,
ConfirmTossPaymentResponse response) {
return RequestConfirmPaymentResponse.builder()
.payment(payment)
.paymentKey(response.paymentKey())
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@
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.dto.payment.RequestConfirmPaymentResponse;
import com.moabam.api.infrastructure.payment.TossPaymentService;
import com.moabam.global.error.exception.NotFoundException;
import com.moabam.global.error.exception.TossPaymentException;

import lombok.RequiredArgsConstructor;

Expand All @@ -36,28 +38,29 @@ public void request(Long memberId, Long paymentId, PaymentRequest request) {
payment.request(request.orderId());
}

public Payment validateInfo(Long memberId, ConfirmPaymentRequest request) {
public RequestConfirmPaymentResponse requestConfirm(Long memberId, ConfirmPaymentRequest request) {
Payment payment = getByOrderId(request.orderId());
payment.validateInfo(memberId, request.amount());

return payment;
try {
ConfirmTossPaymentResponse response = tossPaymentService.confirm(request);
return PaymentMapper.toRequestConfirmPaymentResponse(payment, response);
} catch (TossPaymentException exception) {
payment.fail(request.paymentKey());
throw exception;
}
}

@Transactional
public void confirm(Long memberId, Payment payment, ConfirmTossPaymentResponse response) {
payment.confirm(response.paymentKey());
public void confirm(Long memberId, Payment payment, String paymentKey) {
payment.confirm(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) {
return paymentRepository.findById(paymentId)
.orElseThrow(() -> new NotFoundException(PAYMENT_NOT_FOUND));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.moabam.api.dto.payment;

import com.moabam.api.domain.payment.Payment;

import lombok.Builder;

@Builder
public record RequestConfirmPaymentResponse(
Payment payment,
String paymentKey
) {

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
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;

Expand All @@ -21,7 +20,6 @@
import reactor.core.publisher.Mono;

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

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

@Transactional
public ConfirmTossPaymentResponse confirm(ConfirmPaymentRequest request) {
return webClient.post()
.uri("/v1/payments/confirm")
Expand Down
16 changes: 3 additions & 13 deletions src/main/java/com/moabam/api/presentation/PaymentController.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,11 @@
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.api.dto.payment.RequestConfirmPaymentResponse;
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 @@ -27,7 +24,6 @@
public class PaymentController {

private final PaymentService paymentService;
private final TossPaymentService tossPaymentService;

@PostMapping("/{paymentId}")
@ResponseStatus(HttpStatus.OK)
Expand All @@ -39,13 +35,7 @@ public void request(@Auth AuthMember member, @PathVariable Long paymentId,
@PostMapping("/confirm")
@ResponseStatus(HttpStatus.OK)
public void confirm(@Auth AuthMember member, @Valid @RequestBody ConfirmPaymentRequest 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());
}
RequestConfirmPaymentResponse response = paymentService.requestConfirm(member.id(), request);
paymentService.confirm(member.id(), response.payment(), response.paymentKey());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
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 @@ -18,12 +19,14 @@
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.NotFoundException;
import com.moabam.global.error.exception.TossPaymentException;

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

@Mock
TossPaymentService tossPaymentService;

@Mock
PaymentRepository paymentRepository;

Expand All @@ -58,32 +64,52 @@ void request_not_found_exception() {
.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 RequestConfirm {

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

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

@DisplayName("토스 결제 승인 요청이 실패하면 결제 실패 처리한다.")
@Test
void toss_fail() {
// given
Long memberId = 1L;
Payment payment = payment(bugProduct());
ConfirmPaymentRequest request = confirmPaymentRequest();
given(paymentSearchRepository.findByOrderId(request.orderId())).willReturn(Optional.of(payment));
given(tossPaymentService.confirm(request)).willThrow(TossPaymentException.class);

// when, then
assertThatThrownBy(() -> paymentService.requestConfirm(memberId, request))
.isInstanceOf(TossPaymentException.class);
assertThat(payment.getPaymentKey()).isEqualTo(PAYMENT_KEY);
assertThat(payment.getStatus()).isEqualTo(PaymentStatus.ABORTED);
}
}

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

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

// then
verify(couponService, times(1)).discount(couponWalletId, memberId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,10 +172,8 @@ void confirm_toss_exception() throws Exception {
mockMvc.perform(post("/payments/confirm")
.contentType(APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)))
.andExpect(status().isOk())
.andExpect(status().isInternalServerError())
.andDo(print());
assertThat(payment.getPaymentKey()).isEqualTo(PAYMENT_KEY);
assertThat(payment.getStatus()).isEqualTo(PaymentStatus.ABORTED);
}
}
}

0 comments on commit 5f07beb

Please sign in to comment.