From 8aa3ec14e118ae83f980612ebb99441f72659f5a Mon Sep 17 00:00:00 2001
From: Laptop-Limjihoon <lonelynight1026@gmail.com>
Date: Mon, 5 Feb 2024 04:37:58 +0900
Subject: [PATCH 1/9] =?UTF-8?q?feat=20:=20TossPaymentEventListener=20?=
 =?UTF-8?q?=EB=A5=BC=20=ED=86=B5=ED=95=B4=20TossAPI=20=ED=95=B8=EB=93=A4?=
 =?UTF-8?q?=EB=A7=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../event/TossPaymentEventListener.java       | 42 +++++++++++++++++++
 1 file changed, 42 insertions(+)
 create mode 100644 src/main/java/bc1/gream/domain/payment/toss/service/event/TossPaymentEventListener.java

diff --git a/src/main/java/bc1/gream/domain/payment/toss/service/event/TossPaymentEventListener.java b/src/main/java/bc1/gream/domain/payment/toss/service/event/TossPaymentEventListener.java
new file mode 100644
index 00000000..fc9729ff
--- /dev/null
+++ b/src/main/java/bc1/gream/domain/payment/toss/service/event/TossPaymentEventListener.java
@@ -0,0 +1,42 @@
+package bc1.gream.domain.payment.toss.service.event;
+
+import bc1.gream.domain.payment.toss.dto.response.TossPaymentSuccessResponseDto;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+import java.util.Collections;
+import net.minidev.json.JSONObject;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.event.TransactionalEventListener;
+import org.springframework.web.client.RestTemplate;
+
+@Component
+public class TossPaymentEventListener {
+
+    @TransactionalEventListener
+    public void handleTossPaymentSuccess(TossPaymentSuccessEvent event) {
+        RestTemplate rest = new RestTemplate();
+        HttpHeaders headers = new HttpHeaders();
+
+        String testSecretApiKey = event.getTestSecretApiKey() + ":";
+        String encodedAuth = new String(Base64.getEncoder().encode(testSecretApiKey.getBytes(StandardCharsets.UTF_8)));
+        headers.setBasicAuth(encodedAuth);
+        headers.setContentType(MediaType.APPLICATION_JSON);
+        headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
+
+        JSONObject param = new JSONObject();
+        param.put("orderId", event.getOrderId());
+        param.put("amount", event.getAmount());
+
+        TossPaymentSuccessResponseDto responseDto = rest.postForObject(
+            event.getSuccessUrl() + event.getPaymentKey(),
+            new HttpEntity<>(param, headers),
+            TossPaymentSuccessResponseDto.class
+        );
+
+        // Use the functional interface to pass the result back to TossPaymentController
+        event.getCallback().handle(responseDto);
+    }
+}

From 8806e02e68559cd61af5665ebd4d5e3a8c857e30 Mon Sep 17 00:00:00 2001
From: Laptop-Limjihoon <lonelynight1026@gmail.com>
Date: Mon, 5 Feb 2024 04:38:13 +0900
Subject: [PATCH 2/9] =?UTF-8?q?feat=20:=20TossPaymentSuccessEvent=20?=
 =?UTF-8?q?=EA=B5=AC=ED=98=84?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../event/TossPaymentSuccessEvent.java        | 27 +++++++++++++++++++
 1 file changed, 27 insertions(+)
 create mode 100644 src/main/java/bc1/gream/domain/payment/toss/service/event/TossPaymentSuccessEvent.java

diff --git a/src/main/java/bc1/gream/domain/payment/toss/service/event/TossPaymentSuccessEvent.java b/src/main/java/bc1/gream/domain/payment/toss/service/event/TossPaymentSuccessEvent.java
new file mode 100644
index 00000000..179f4fe1
--- /dev/null
+++ b/src/main/java/bc1/gream/domain/payment/toss/service/event/TossPaymentSuccessEvent.java
@@ -0,0 +1,27 @@
+package bc1.gream.domain.payment.toss.service.event;
+
+import lombok.Getter;
+import org.springframework.context.ApplicationEvent;
+
+@Getter
+public class TossPaymentSuccessEvent extends ApplicationEvent {
+
+    private final String paymentKey;
+    private final Long orderId;
+    private final Long amount;
+    private final String successUrl;
+    private final String testSecretApiKey;
+
+    private final TossPaymentSuccessCallback callback;
+
+    public TossPaymentSuccessEvent(Object source, String paymentKey, Long orderId, Long amount, String successUrl, String testSecretApiKey,
+        TossPaymentSuccessCallback callback) {
+        super(source);
+        this.paymentKey = paymentKey;
+        this.orderId = orderId;
+        this.amount = amount;
+        this.successUrl = successUrl;
+        this.testSecretApiKey = testSecretApiKey;
+        this.callback = callback;
+    }
+}

From f53e29496becfca0f1bbada1a4e1fbf060acb4dd Mon Sep 17 00:00:00 2001
From: Laptop-Limjihoon <lonelynight1026@gmail.com>
Date: Mon, 5 Feb 2024 04:38:22 +0900
Subject: [PATCH 3/9] =?UTF-8?q?feat=20:=20TossPaymentSuccessCallback=20?=
 =?UTF-8?q?=EA=B5=AC=ED=98=84?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../toss/service/event/TossPaymentSuccessCallback.java   | 9 +++++++++
 1 file changed, 9 insertions(+)
 create mode 100644 src/main/java/bc1/gream/domain/payment/toss/service/event/TossPaymentSuccessCallback.java

diff --git a/src/main/java/bc1/gream/domain/payment/toss/service/event/TossPaymentSuccessCallback.java b/src/main/java/bc1/gream/domain/payment/toss/service/event/TossPaymentSuccessCallback.java
new file mode 100644
index 00000000..8e4d63c3
--- /dev/null
+++ b/src/main/java/bc1/gream/domain/payment/toss/service/event/TossPaymentSuccessCallback.java
@@ -0,0 +1,9 @@
+package bc1.gream.domain.payment.toss.service.event;
+
+import bc1.gream.domain.payment.toss.dto.response.TossPaymentSuccessResponseDto;
+
+@FunctionalInterface
+public interface TossPaymentSuccessCallback {
+
+    void handle(TossPaymentSuccessResponseDto responseDto);
+}

From 774931eec575465d3eb67f573360bc01b619ae1b Mon Sep 17 00:00:00 2001
From: Laptop-Limjihoon <lonelynight1026@gmail.com>
Date: Mon, 5 Feb 2024 04:39:26 +0900
Subject: [PATCH 4/9] =?UTF-8?q?feat=20:=20PaymentService=20=EC=97=90?=
 =?UTF-8?q?=EC=84=9C=20ApplicationEventPublisher=20=EC=9D=84=20=ED=86=B5?=
 =?UTF-8?q?=ED=95=B4=20TossPaymentSuccessEvent=20=ED=98=B8=EC=B6=9C?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 ...aymentService.java => PaymentService.java} | 59 +++++--------------
 1 file changed, 15 insertions(+), 44 deletions(-)
 rename src/main/java/bc1/gream/domain/payment/toss/service/{TossPaymentService.java => PaymentService.java} (65%)

diff --git a/src/main/java/bc1/gream/domain/payment/toss/service/TossPaymentService.java b/src/main/java/bc1/gream/domain/payment/toss/service/PaymentService.java
similarity index 65%
rename from src/main/java/bc1/gream/domain/payment/toss/service/TossPaymentService.java
rename to src/main/java/bc1/gream/domain/payment/toss/service/PaymentService.java
index 8e95a85a..91c07374 100644
--- a/src/main/java/bc1/gream/domain/payment/toss/service/TossPaymentService.java
+++ b/src/main/java/bc1/gream/domain/payment/toss/service/PaymentService.java
@@ -2,30 +2,25 @@
 
 import bc1.gream.domain.payment.toss.dto.response.TossPaymentFailResponseDto;
 import bc1.gream.domain.payment.toss.dto.response.TossPaymentInitialResponseDto;
-import bc1.gream.domain.payment.toss.dto.response.TossPaymentSuccessResponseDto;
 import bc1.gream.domain.payment.toss.entity.TossPayment;
 import bc1.gream.domain.payment.toss.mapper.TossPaymentMapper;
 import bc1.gream.domain.payment.toss.repository.TossPaymentRepository;
+import bc1.gream.domain.payment.toss.service.event.TossPaymentSuccessCallback;
+import bc1.gream.domain.payment.toss.service.event.TossPaymentSuccessEvent;
 import bc1.gream.global.common.ResultCase;
 import bc1.gream.global.exception.GlobalException;
-import java.nio.charset.StandardCharsets;
-import java.util.Base64;
-import java.util.Collections;
 import lombok.RequiredArgsConstructor;
-import net.minidev.json.JSONObject;
 import org.springframework.beans.factory.annotation.Value;
-import org.springframework.http.HttpEntity;
-import org.springframework.http.HttpHeaders;
-import org.springframework.http.MediaType;
+import org.springframework.context.ApplicationEventPublisher;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
-import org.springframework.web.client.RestTemplate;
 
 @Service
 @RequiredArgsConstructor
-public class TossPaymentService {
+public class PaymentService {
 
     private final TossPaymentRepository tossPaymentRepository;
+    private final ApplicationEventPublisher eventPublisher;
 
     @Value("${payment.toss.test_client_api_key}")
     private String testClientApiKey;
@@ -57,9 +52,16 @@ public TossPaymentInitialResponseDto requestTossPayment(TossPayment tossPayment)
      * @return 토스페이 최종요청 결과
      */
     @Transactional
-    public TossPaymentSuccessResponseDto requestFinalTossPayment(String paymentKey, Long orderId, Long amount) {
+    public void requestFinalTossPayment(String paymentKey, Long orderId, Long amount, TossPaymentSuccessCallback callback) {
         this.verifyRequest(paymentKey, orderId, amount);
-        return this.sendFinalRequestToTossApi(paymentKey, orderId, amount);
+        eventPublisher.publishEvent(new TossPaymentSuccessEvent(
+            this,
+            paymentKey,
+            orderId,
+            amount,
+            successUrl,
+            testSecretApiKey,
+            callback)); // Provide method reference to the event listener
     }
 
     /**
@@ -88,7 +90,7 @@ public TossPaymentFailResponseDto requestFail(String errorCode, String errorMsg,
     @Transactional
     void verifyRequest(String paymentKey, Long orderId, Long amount) {
         // 주문아이디 일치 검증
-        TossPayment tossPayment = findBy(orderId);
+        TossPayment tossPayment = this.findBy(orderId);
         // 결제금액 일치 검증
         if (tossPayment.getAmount().equals(amount)) {
             tossPayment.setPaymentKey(paymentKey);
@@ -97,37 +99,6 @@ void verifyRequest(String paymentKey, Long orderId, Long amount) {
         throw new GlobalException(ResultCase.UNMATCHED_PAYMENT_AMOUNT);
     }
 
-    /**
-     * 토스페이API에 POST요청
-     *
-     * @param paymentKey 토스 결제고유번호
-     * @param orderId    서버 주문고유번호
-     * @param amount     결제액
-     * @return 토스페이API 요청결과
-     */
-    @Transactional
-    TossPaymentSuccessResponseDto sendFinalRequestToTossApi(String paymentKey, Long orderId, Long amount) {
-        RestTemplate rest = new RestTemplate();
-
-        HttpHeaders headers = new HttpHeaders();
-
-        testSecretApiKey = testSecretApiKey + ":";
-        String encodedAuth = new String(Base64.getEncoder().encode(testSecretApiKey.getBytes(StandardCharsets.UTF_8)));
-        headers.setBasicAuth(encodedAuth);
-        headers.setContentType(MediaType.APPLICATION_JSON);
-        headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
-
-        JSONObject param = new JSONObject();
-        param.put("orderId", orderId);
-        param.put("amount", amount);
-
-        return rest.postForObject(
-            successUrl + paymentKey,
-            new HttpEntity<>(param, headers),
-            TossPaymentSuccessResponseDto.class
-        );
-    }
-
     @Transactional(readOnly = true)
     TossPayment findBy(Long orderId) {
         return tossPaymentRepository.findByOrderId(orderId)

From cd1c593f42f8aeca05630f90e58b4deedd3e2be7 Mon Sep 17 00:00:00 2001
From: Laptop-Limjihoon <lonelynight1026@gmail.com>
Date: Mon, 5 Feb 2024 04:40:31 +0900
Subject: [PATCH 5/9] =?UTF-8?q?feat=20:=20TossPaymentController=20?=
 =?UTF-8?q?=EC=97=90=EC=84=9C=20Semaphore=EB=A5=BC=20=ED=86=B5=ED=95=B4=20?=
 =?UTF-8?q?=EC=9D=B4=EB=B2=A4=ED=8A=B8=20=EC=B2=98=EB=A6=AC=EC=97=90=20?=
 =?UTF-8?q?=EB=94=B0=EB=A5=B8=20callback=20=EC=B2=98=EB=A6=AC?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../controller/TossPaymentController.java     | 25 +++++++++++++------
 1 file changed, 18 insertions(+), 7 deletions(-)

diff --git a/src/main/java/bc1/gream/domain/payment/toss/controller/TossPaymentController.java b/src/main/java/bc1/gream/domain/payment/toss/controller/TossPaymentController.java
index d03986e4..a8396c8f 100644
--- a/src/main/java/bc1/gream/domain/payment/toss/controller/TossPaymentController.java
+++ b/src/main/java/bc1/gream/domain/payment/toss/controller/TossPaymentController.java
@@ -6,13 +6,15 @@
 import bc1.gream.domain.payment.toss.dto.response.TossPaymentSuccessResponseDto;
 import bc1.gream.domain.payment.toss.entity.TossPayment;
 import bc1.gream.domain.payment.toss.mapper.TossPaymentMapper;
-import bc1.gream.domain.payment.toss.service.TossPaymentService;
+import bc1.gream.domain.payment.toss.service.PaymentService;
 import bc1.gream.domain.payment.toss.validator.TossPaymentRequestValidator;
 import bc1.gream.global.common.RestResponse;
 import bc1.gream.global.security.UserDetailsImpl;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.media.Schema;
 import jakarta.validation.Valid;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.atomic.AtomicReference;
 import lombok.RequiredArgsConstructor;
 import org.springframework.security.core.annotation.AuthenticationPrincipal;
 import org.springframework.web.bind.annotation.GetMapping;
@@ -27,7 +29,7 @@
 @RequiredArgsConstructor
 public class TossPaymentController {
 
-    private final TossPaymentService tossPaymentService;
+    private final PaymentService paymentService;
 
     @PostMapping("/request")
     @Operation(summary = "토스페이 결제 검증/확인 요청", description = "결제정보에 대한 검증/확인 이후 필요한 값들을 반환합니다.")
@@ -37,7 +39,7 @@ public RestResponse<TossPaymentInitialResponseDto> requestTossPayment(
     ) {
         TossPaymentRequestValidator.validate(requestDto);
         TossPayment payment = TossPaymentMapper.INSTANCE.fromTossPaymentInitialRequestDto(userDetails.getUser(), requestDto);
-        TossPaymentInitialResponseDto responseDto = tossPaymentService.requestTossPayment(payment);
+        TossPaymentInitialResponseDto responseDto = paymentService.requestTossPayment(payment);
         return RestResponse.success(responseDto);
     }
 
@@ -47,9 +49,18 @@ public RestResponse<TossPaymentSuccessResponseDto> requestFinalTossPayment(
         @Schema(description = "토스 결제고유번호") @RequestParam String paymentKey,
         @Schema(description = "서버 주분고유번호") @RequestParam Long orderId,
         @Schema(description = "결제금액") @RequestParam Long amount
-    ) {
-        TossPaymentSuccessResponseDto responseDto = tossPaymentService.requestFinalTossPayment(paymentKey, orderId, amount);
-        return RestResponse.success(responseDto);
+    ) throws InterruptedException {
+        AtomicReference<TossPaymentSuccessResponseDto> responseDtoHolder = new AtomicReference<>();
+        Semaphore semaphore = new Semaphore(0);
+
+        paymentService.requestFinalTossPayment(paymentKey, orderId, amount, responseDto -> {
+            responseDtoHolder.set(responseDto);
+            semaphore.release();
+        });
+
+        semaphore.acquire();
+
+        return RestResponse.success(responseDtoHolder.get());
     }
 
     @GetMapping("/fail")
@@ -59,7 +70,7 @@ public RestResponse<TossPaymentFailResponseDto> requestFail(
         @Schema(description = "에러 메세지") @RequestParam String errorMsg,
         @Schema(description = "서버 주문고유번호") @RequestParam Long orderId
     ) {
-        TossPaymentFailResponseDto responseDto = tossPaymentService.requestFail(errorCode, errorMsg, orderId);
+        TossPaymentFailResponseDto responseDto = paymentService.requestFail(errorCode, errorMsg, orderId);
         return RestResponse.success(responseDto);
     }
 }

From dbf37882402073532c87e33a176a6ecab6daa2d0 Mon Sep 17 00:00:00 2001
From: Laptop-Limjihoon <lonelynight1026@gmail.com>
Date: Mon, 5 Feb 2024 04:40:47 +0900
Subject: [PATCH 6/9] =?UTF-8?q?Revert=20"feat=20:=20TossPaymentController?=
 =?UTF-8?q?=20=EC=97=90=EC=84=9C=20Semaphore=EB=A5=BC=20=ED=86=B5=ED=95=B4?=
 =?UTF-8?q?=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20=EC=B2=98=EB=A6=AC=EC=97=90=20?=
 =?UTF-8?q?=EB=94=B0=EB=A5=B8=20callback=20=EC=B2=98=EB=A6=AC"?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This reverts commit cd1c593f42f8aeca05630f90e58b4deedd3e2be7.
---
 .../controller/TossPaymentController.java     | 25 ++++++-------------
 1 file changed, 7 insertions(+), 18 deletions(-)

diff --git a/src/main/java/bc1/gream/domain/payment/toss/controller/TossPaymentController.java b/src/main/java/bc1/gream/domain/payment/toss/controller/TossPaymentController.java
index a8396c8f..d03986e4 100644
--- a/src/main/java/bc1/gream/domain/payment/toss/controller/TossPaymentController.java
+++ b/src/main/java/bc1/gream/domain/payment/toss/controller/TossPaymentController.java
@@ -6,15 +6,13 @@
 import bc1.gream.domain.payment.toss.dto.response.TossPaymentSuccessResponseDto;
 import bc1.gream.domain.payment.toss.entity.TossPayment;
 import bc1.gream.domain.payment.toss.mapper.TossPaymentMapper;
-import bc1.gream.domain.payment.toss.service.PaymentService;
+import bc1.gream.domain.payment.toss.service.TossPaymentService;
 import bc1.gream.domain.payment.toss.validator.TossPaymentRequestValidator;
 import bc1.gream.global.common.RestResponse;
 import bc1.gream.global.security.UserDetailsImpl;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.media.Schema;
 import jakarta.validation.Valid;
-import java.util.concurrent.Semaphore;
-import java.util.concurrent.atomic.AtomicReference;
 import lombok.RequiredArgsConstructor;
 import org.springframework.security.core.annotation.AuthenticationPrincipal;
 import org.springframework.web.bind.annotation.GetMapping;
@@ -29,7 +27,7 @@
 @RequiredArgsConstructor
 public class TossPaymentController {
 
-    private final PaymentService paymentService;
+    private final TossPaymentService tossPaymentService;
 
     @PostMapping("/request")
     @Operation(summary = "토스페이 결제 검증/확인 요청", description = "결제정보에 대한 검증/확인 이후 필요한 값들을 반환합니다.")
@@ -39,7 +37,7 @@ public RestResponse<TossPaymentInitialResponseDto> requestTossPayment(
     ) {
         TossPaymentRequestValidator.validate(requestDto);
         TossPayment payment = TossPaymentMapper.INSTANCE.fromTossPaymentInitialRequestDto(userDetails.getUser(), requestDto);
-        TossPaymentInitialResponseDto responseDto = paymentService.requestTossPayment(payment);
+        TossPaymentInitialResponseDto responseDto = tossPaymentService.requestTossPayment(payment);
         return RestResponse.success(responseDto);
     }
 
@@ -49,18 +47,9 @@ public RestResponse<TossPaymentSuccessResponseDto> requestFinalTossPayment(
         @Schema(description = "토스 결제고유번호") @RequestParam String paymentKey,
         @Schema(description = "서버 주분고유번호") @RequestParam Long orderId,
         @Schema(description = "결제금액") @RequestParam Long amount
-    ) throws InterruptedException {
-        AtomicReference<TossPaymentSuccessResponseDto> responseDtoHolder = new AtomicReference<>();
-        Semaphore semaphore = new Semaphore(0);
-
-        paymentService.requestFinalTossPayment(paymentKey, orderId, amount, responseDto -> {
-            responseDtoHolder.set(responseDto);
-            semaphore.release();
-        });
-
-        semaphore.acquire();
-
-        return RestResponse.success(responseDtoHolder.get());
+    ) {
+        TossPaymentSuccessResponseDto responseDto = tossPaymentService.requestFinalTossPayment(paymentKey, orderId, amount);
+        return RestResponse.success(responseDto);
     }
 
     @GetMapping("/fail")
@@ -70,7 +59,7 @@ public RestResponse<TossPaymentFailResponseDto> requestFail(
         @Schema(description = "에러 메세지") @RequestParam String errorMsg,
         @Schema(description = "서버 주문고유번호") @RequestParam Long orderId
     ) {
-        TossPaymentFailResponseDto responseDto = paymentService.requestFail(errorCode, errorMsg, orderId);
+        TossPaymentFailResponseDto responseDto = tossPaymentService.requestFail(errorCode, errorMsg, orderId);
         return RestResponse.success(responseDto);
     }
 }

From afe0ab7e1ed62168d4b4b652c5011b95a7756751 Mon Sep 17 00:00:00 2001
From: Laptop-Limjihoon <lonelynight1026@gmail.com>
Date: Mon, 5 Feb 2024 04:41:49 +0900
Subject: [PATCH 7/9] =?UTF-8?q?feat=20:=20TossPaymentController=20?=
 =?UTF-8?q?=EC=97=90=EC=84=9C=20Semaphore=EB=A5=BC=20=ED=86=B5=ED=95=B4=20?=
 =?UTF-8?q?=EC=9D=B4=EB=B2=A4=ED=8A=B8=20=EC=B2=98=EB=A6=AC=EC=97=90=20?=
 =?UTF-8?q?=EB=94=B0=EB=A5=B8=20callback=20=EC=B2=98=EB=A6=AC?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../controller/TossPaymentController.java     | 25 +++++++++++++-----
 .../controller/TossPaymentControllerTest.java | 26 ++++++++++++-------
 2 files changed, 35 insertions(+), 16 deletions(-)

diff --git a/src/main/java/bc1/gream/domain/payment/toss/controller/TossPaymentController.java b/src/main/java/bc1/gream/domain/payment/toss/controller/TossPaymentController.java
index d03986e4..a8396c8f 100644
--- a/src/main/java/bc1/gream/domain/payment/toss/controller/TossPaymentController.java
+++ b/src/main/java/bc1/gream/domain/payment/toss/controller/TossPaymentController.java
@@ -6,13 +6,15 @@
 import bc1.gream.domain.payment.toss.dto.response.TossPaymentSuccessResponseDto;
 import bc1.gream.domain.payment.toss.entity.TossPayment;
 import bc1.gream.domain.payment.toss.mapper.TossPaymentMapper;
-import bc1.gream.domain.payment.toss.service.TossPaymentService;
+import bc1.gream.domain.payment.toss.service.PaymentService;
 import bc1.gream.domain.payment.toss.validator.TossPaymentRequestValidator;
 import bc1.gream.global.common.RestResponse;
 import bc1.gream.global.security.UserDetailsImpl;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.media.Schema;
 import jakarta.validation.Valid;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.atomic.AtomicReference;
 import lombok.RequiredArgsConstructor;
 import org.springframework.security.core.annotation.AuthenticationPrincipal;
 import org.springframework.web.bind.annotation.GetMapping;
@@ -27,7 +29,7 @@
 @RequiredArgsConstructor
 public class TossPaymentController {
 
-    private final TossPaymentService tossPaymentService;
+    private final PaymentService paymentService;
 
     @PostMapping("/request")
     @Operation(summary = "토스페이 결제 검증/확인 요청", description = "결제정보에 대한 검증/확인 이후 필요한 값들을 반환합니다.")
@@ -37,7 +39,7 @@ public RestResponse<TossPaymentInitialResponseDto> requestTossPayment(
     ) {
         TossPaymentRequestValidator.validate(requestDto);
         TossPayment payment = TossPaymentMapper.INSTANCE.fromTossPaymentInitialRequestDto(userDetails.getUser(), requestDto);
-        TossPaymentInitialResponseDto responseDto = tossPaymentService.requestTossPayment(payment);
+        TossPaymentInitialResponseDto responseDto = paymentService.requestTossPayment(payment);
         return RestResponse.success(responseDto);
     }
 
@@ -47,9 +49,18 @@ public RestResponse<TossPaymentSuccessResponseDto> requestFinalTossPayment(
         @Schema(description = "토스 결제고유번호") @RequestParam String paymentKey,
         @Schema(description = "서버 주분고유번호") @RequestParam Long orderId,
         @Schema(description = "결제금액") @RequestParam Long amount
-    ) {
-        TossPaymentSuccessResponseDto responseDto = tossPaymentService.requestFinalTossPayment(paymentKey, orderId, amount);
-        return RestResponse.success(responseDto);
+    ) throws InterruptedException {
+        AtomicReference<TossPaymentSuccessResponseDto> responseDtoHolder = new AtomicReference<>();
+        Semaphore semaphore = new Semaphore(0);
+
+        paymentService.requestFinalTossPayment(paymentKey, orderId, amount, responseDto -> {
+            responseDtoHolder.set(responseDto);
+            semaphore.release();
+        });
+
+        semaphore.acquire();
+
+        return RestResponse.success(responseDtoHolder.get());
     }
 
     @GetMapping("/fail")
@@ -59,7 +70,7 @@ public RestResponse<TossPaymentFailResponseDto> requestFail(
         @Schema(description = "에러 메세지") @RequestParam String errorMsg,
         @Schema(description = "서버 주문고유번호") @RequestParam Long orderId
     ) {
-        TossPaymentFailResponseDto responseDto = tossPaymentService.requestFail(errorCode, errorMsg, orderId);
+        TossPaymentFailResponseDto responseDto = paymentService.requestFail(errorCode, errorMsg, orderId);
         return RestResponse.success(responseDto);
     }
 }
diff --git a/src/test/java/bc1/gream/domain/payment/toss/controller/TossPaymentControllerTest.java b/src/test/java/bc1/gream/domain/payment/toss/controller/TossPaymentControllerTest.java
index 0598302e..cc41f265 100644
--- a/src/test/java/bc1/gream/domain/payment/toss/controller/TossPaymentControllerTest.java
+++ b/src/test/java/bc1/gream/domain/payment/toss/controller/TossPaymentControllerTest.java
@@ -1,7 +1,9 @@
 package bc1.gream.domain.payment.toss.controller;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.when;
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
@@ -17,7 +19,8 @@
 import bc1.gream.domain.payment.toss.entity.OrderName;
 import bc1.gream.domain.payment.toss.entity.PayType;
 import bc1.gream.domain.payment.toss.entity.TossPayment;
-import bc1.gream.domain.payment.toss.service.TossPaymentService;
+import bc1.gream.domain.payment.toss.service.PaymentService;
+import bc1.gream.domain.payment.toss.service.event.TossPaymentSuccessCallback;
 import bc1.gream.global.security.WithMockCustomUser;
 import bc1.gream.test.UserTest;
 import com.fasterxml.jackson.core.type.TypeReference;
@@ -46,7 +49,7 @@ class TossPaymentControllerTest {
     @Autowired
     private ObjectMapper objectMapper;
     @MockBean
-    private TossPaymentService tossPaymentService;
+    private PaymentService paymentService;
 
     @BeforeEach
     void setUp() {
@@ -74,7 +77,7 @@ void setUp() {
             .userNickname(UserTest.TEST_USER_NICKNAME)
             .paymentHasSuccess(true)
             .build();
-        given(tossPaymentService.requestTossPayment(any(TossPayment.class)))
+        given(paymentService.requestTossPayment(any(TossPayment.class)))
             .willReturn(responseDto);
 
         // WHEN
@@ -132,12 +135,17 @@ void setUp() {
             .card(expectedPaymentCard)
             .type("NORMAL")
             .build();
+        doAnswer(invocation -> {
+            TossPaymentSuccessCallback callback = invocation.getArgument(3);
+            callback.handle(responseDto);
+            return null;
+        }).when(paymentService).requestFinalTossPayment(
+            eq(paymentKey),
+            eq(orderId),
+            eq(amount),
+            any(TossPaymentSuccessCallback.class));
 
-        // WHEN
-        when(tossPaymentService.requestFinalTossPayment(paymentKey, orderId, amount))
-            .thenReturn(responseDto);
-
-        // THEN
+        // WHEN, THEN
         mockMvc.perform(
                 get(url)
                     .param("paymentKey", paymentKey)
@@ -183,7 +191,7 @@ void setUp() {
             .build();
 
         // WHEN
-        when(tossPaymentService.requestFail(errorCode, errorMsg, orderId))
+        when(paymentService.requestFail(errorCode, errorMsg, orderId))
             .thenReturn(responseDto);
 
         // THEN

From b059c564a1f4b461f992cd507ef4de63da817ec0 Mon Sep 17 00:00:00 2001
From: Laptop-Limjihoon <lonelynight1026@gmail.com>
Date: Mon, 5 Feb 2024 04:43:02 +0900
Subject: [PATCH 8/9] =?UTF-8?q?feat=20:=20GlobalExceptionHandler=20Thread?=
 =?UTF-8?q?=20=EC=98=A4=EB=A5=98=20=EB=8C=80=ED=95=9C=20AOP=20=ED=95=B8?=
 =?UTF-8?q?=EB=93=A4=EB=9F=AC=20=EC=B2=98=EB=A6=AC?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/main/java/bc1/gream/global/common/ResultCase.java |  3 ++-
 .../global/exception/GlobalExceptionHandler.java      | 11 +++++++++++
 2 files changed, 13 insertions(+), 1 deletion(-)

diff --git a/src/main/java/bc1/gream/global/common/ResultCase.java b/src/main/java/bc1/gream/global/common/ResultCase.java
index 9982b14b..0eb80eb4 100644
--- a/src/main/java/bc1/gream/global/common/ResultCase.java
+++ b/src/main/java/bc1/gream/global/common/ResultCase.java
@@ -61,7 +61,8 @@ public enum ResultCase {
     // 시스템 에러 500
     SYSTEM_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, 5002, "알 수 없는 에러가 발생했습니다."),
     // 잘못된 도메인 정렬값 입력 400
-    INVALID_ORDER_CRITERIA(HttpStatus.BAD_REQUEST, 5001, "유효하지 않은 도메인 정렬값"),
+    INVALID_ORDER_CRITERIA(HttpStatus.BAD_REQUEST, 5003, "유효하지 않은 도메인 정렬값"),
+    THREAD_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, 5004, "유효하지 않은 도메인 정렬값"),
 
     // 쿠폰 6000번대
     // 쿠폰이 존재하지 않을 때 404
diff --git a/src/main/java/bc1/gream/global/exception/GlobalExceptionHandler.java b/src/main/java/bc1/gream/global/exception/GlobalExceptionHandler.java
index 61a8970e..9a2f13cf 100644
--- a/src/main/java/bc1/gream/global/exception/GlobalExceptionHandler.java
+++ b/src/main/java/bc1/gream/global/exception/GlobalExceptionHandler.java
@@ -49,6 +49,17 @@ public ResponseEntity<RestResponse<List<InvalidInputResponseDto>>> handlerValida
         return RestResponse.error(ResultCase.INVALID_INPUT, invalidInputList);
     }
 
+    /**
+     * Thread 오류 대한 핸들러
+     *
+     * @param ex Thread 오류에 따른 InterruptedException
+     * @return Thread 에러케이스와 에러리스폰스
+     */
+    @ExceptionHandler(InterruptedException.class)
+    public ResponseEntity<RestResponse<ErrorResponseDto>> handlerInterruptedException(InterruptedException ex) {
+        return RestResponse.error(ResultCase.THREAD_ERROR, new ErrorResponseDto());
+    }
+
     /**
      * Business 오류 발생에 대한 핸들러
      *

From 9620b4ce24c5f8a5b4b9625fa1d87af95b901da9 Mon Sep 17 00:00:00 2001
From: Laptop-Limjihoon <lonelynight1026@gmail.com>
Date: Mon, 5 Feb 2024 05:14:51 +0900
Subject: [PATCH 9/9] =?UTF-8?q?feat=20:=20AsyncConfig=20=EB=A5=BC=20?=
 =?UTF-8?q?=ED=86=B5=ED=95=B4=20TossPaymentEventListener=20=EC=97=90=20@As?=
 =?UTF-8?q?ync=20=EC=A7=80=EC=9B=90?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../toss/service/event/TossPaymentEventListener.java   |  2 ++
 src/main/java/bc1/gream/global/config/AsyncConfig.java | 10 ++++++++++
 2 files changed, 12 insertions(+)
 create mode 100644 src/main/java/bc1/gream/global/config/AsyncConfig.java

diff --git a/src/main/java/bc1/gream/domain/payment/toss/service/event/TossPaymentEventListener.java b/src/main/java/bc1/gream/domain/payment/toss/service/event/TossPaymentEventListener.java
index fc9729ff..54958871 100644
--- a/src/main/java/bc1/gream/domain/payment/toss/service/event/TossPaymentEventListener.java
+++ b/src/main/java/bc1/gream/domain/payment/toss/service/event/TossPaymentEventListener.java
@@ -8,6 +8,7 @@
 import org.springframework.http.HttpEntity;
 import org.springframework.http.HttpHeaders;
 import org.springframework.http.MediaType;
+import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Component;
 import org.springframework.transaction.event.TransactionalEventListener;
 import org.springframework.web.client.RestTemplate;
@@ -15,6 +16,7 @@
 @Component
 public class TossPaymentEventListener {
 
+    @Async
     @TransactionalEventListener
     public void handleTossPaymentSuccess(TossPaymentSuccessEvent event) {
         RestTemplate rest = new RestTemplate();
diff --git a/src/main/java/bc1/gream/global/config/AsyncConfig.java b/src/main/java/bc1/gream/global/config/AsyncConfig.java
new file mode 100644
index 00000000..a236e02e
--- /dev/null
+++ b/src/main/java/bc1/gream/global/config/AsyncConfig.java
@@ -0,0 +1,10 @@
+package bc1.gream.global.config;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.annotation.EnableAsync;
+
+@EnableAsync
+@Configuration
+public class AsyncConfig {
+
+}
\ No newline at end of file