diff --git a/src/main/java/com/example/silverbridgeX_user/global/api_payload/ErrorCode.java b/src/main/java/com/example/silverbridgeX_user/global/api_payload/ErrorCode.java index 70f7b7a..625660e 100644 --- a/src/main/java/com/example/silverbridgeX_user/global/api_payload/ErrorCode.java +++ b/src/main/java/com/example/silverbridgeX_user/global/api_payload/ErrorCode.java @@ -11,6 +11,8 @@ public enum ErrorCode implements BaseCode { // Common BAD_REQUEST(HttpStatus.BAD_REQUEST, "COMMON_400", "잘못된 요청입니다."), INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "COMMON_500", "서버 에러, 서버 개발자에게 문의하세요."), + EXTERNAL_API_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "PAYMENT_5001", "외부 API 호출 중 오류가 발생했습니다."), + // User USER_NOT_FOUND(HttpStatus.NOT_FOUND, "USER_4041", "존재하지 않는 회원입니다."), @@ -37,6 +39,8 @@ public enum ErrorCode implements BaseCode { TID_NOT_EXIST(HttpStatus.BAD_REQUEST, "PAYMENT_4001", "tid가 존재하지 않습니다."), TID_SID_UNSUPPORTED(HttpStatus.BAD_REQUEST, "PAYMENT_4002", "지원되지 않는 tid, sid 입니다."), SID_NOT_EXIST(HttpStatus.BAD_REQUEST, "PAYMENT_4003", "sid가 존재하지 않습니다."), + TID_ALREADY_EXIST(HttpStatus.BAD_REQUEST, "PAYMENT_4004", "tid가 이미 존재합니다."), + PAYMENT_FAILED(HttpStatus.BAD_REQUEST, "PAYMENT_4005", "결제에 실패하였습니다."), // Match MATCH_NOT_EXIST(HttpStatus.BAD_REQUEST, "MATCH_4001", "매치 신청 정보가 존재하지 않습니다."), diff --git a/src/main/java/com/example/silverbridgeX_user/global/util/RestTemplateUtil.java b/src/main/java/com/example/silverbridgeX_user/global/util/RestTemplateUtil.java new file mode 100644 index 0000000..f733051 --- /dev/null +++ b/src/main/java/com/example/silverbridgeX_user/global/util/RestTemplateUtil.java @@ -0,0 +1,22 @@ +package com.example.silverbridgeX_user.global.util; + +import java.util.Map; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestTemplate; + +@Component +@RequiredArgsConstructor +public class RestTemplateUtil { + + private final RestTemplate restTemplate; + + public T post(String url, Map parameters, HttpHeaders headers, Class responseType) { + HttpEntity> requestEntity = new HttpEntity<>(parameters, headers); + ResponseEntity response = restTemplate.postForEntity(url, requestEntity, responseType); + return response.getBody(); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/silverbridgeX_user/payment/controller/PaymentController.java b/src/main/java/com/example/silverbridgeX_user/payment/controller/PaymentController.java index f6ae878..2e121ad 100644 --- a/src/main/java/com/example/silverbridgeX_user/payment/controller/PaymentController.java +++ b/src/main/java/com/example/silverbridgeX_user/payment/controller/PaymentController.java @@ -2,17 +2,19 @@ import com.example.silverbridgeX_user.global.api_payload.ApiResponse; import com.example.silverbridgeX_user.global.api_payload.SuccessCode; -import com.example.silverbridgeX_user.payment.converter.PaymentConverter; import com.example.silverbridgeX_user.payment.domain.Payment; import com.example.silverbridgeX_user.payment.dto.PaymentDto; import com.example.silverbridgeX_user.payment.service.PaymentService; -import com.example.silverbridgeX_user.user.domain.User; import com.example.silverbridgeX_user.user.jwt.CustomUserDetails; import com.example.silverbridgeX_user.user.service.UserService; import io.swagger.v3.oas.annotations.Operation; import lombok.RequiredArgsConstructor; import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.ModelAndView; @RestController @@ -25,11 +27,12 @@ public class PaymentController { @PostMapping("/ready") @Operation(summary = "카카오페이 URL 생성 API", description = "카카오페이 URL을 생성하는 API 입니다.") - public ApiResponse readyToKakaoPay(@AuthenticationPrincipal CustomUserDetails customUserDetails) { - PaymentDto.KakaoReadyResponse kakaoReadyResponse = paymentService.kakaoPayReady(); + public ApiResponse readyToKakaoPay( + @AuthenticationPrincipal CustomUserDetails customUserDetails + ) { + Long userId = userService.findByUserName(customUserDetails.getUsername()).getId(); - User user = userService.findByUserName(customUserDetails.getUsername()); - Long userId = user.getId(); + PaymentDto.KakaoReadyResponse kakaoReadyResponse = paymentService.kakaoPayReady(userId); paymentService.saveTid(userId, kakaoReadyResponse.getTid()); @@ -38,11 +41,12 @@ public ApiResponse readyToKakaoPay(@Authenticatio @PostMapping("/ready/key") @Operation(summary = "카카오페이 URL 생성 API", description = "key를 이용하여 카카오페이 URL을 생성하는 API 입니다.") - public ApiResponse readyToKakaoPay(@RequestParam("id") String key) { - PaymentDto.KakaoReadyResponse kakaoReadyResponse = paymentService.kakaoPayReady(); + public ApiResponse readyToKakaoPay( + @RequestParam("id") String key + ) { + Long userId = userService.findByUserName(key).getId(); - User user = userService.findByUserName(key); - Long userId = user.getId(); + PaymentDto.KakaoReadyResponse kakaoReadyResponse = paymentService.kakaoPayReady(userId); paymentService.saveTid(userId, kakaoReadyResponse.getTid()); @@ -50,8 +54,10 @@ public ApiResponse readyToKakaoPay(@RequestParam( } @GetMapping("/success") - public ModelAndView afterPayRequest(@RequestParam("pg_token") String pgToken) { - PaymentDto.KakaoApproveResponse kakaoApproveResponse = paymentService.approveResponse(pgToken); + public ModelAndView afterPayRequest( + @RequestParam("pg_token") String pgToken, @RequestParam("userId") Long userId + ) { + PaymentDto.KakaoApproveResponse kakaoApproveResponse = paymentService.approveResponse(pgToken, userId); ModelAndView modelAndView = new ModelAndView("success"); // "success"는 템플릿 파일 이름 modelAndView.addObject("paymentInfo", kakaoApproveResponse); @@ -67,10 +73,11 @@ public String fail() { } @GetMapping("/cancel") - public ApiResponse refund(@AuthenticationPrincipal CustomUserDetails customUserDetails) { + public ApiResponse refund( + @AuthenticationPrincipal CustomUserDetails customUserDetails + ) { - User user = userService.findByUserName(customUserDetails.getUsername()); - Long userId = user.getId(); + Long userId = userService.findByUserName(customUserDetails.getUsername()).getId(); Payment kakaoPay = paymentService.getKakaoPayInfo(userId); @@ -82,10 +89,11 @@ public ApiResponse refund(@AuthenticationPrincip } @GetMapping("/cancel/key") - public ApiResponse refund(@RequestParam("id") String key) { + public ApiResponse refund( + @RequestParam("id") String key + ) { - User user = userService.findByUserName(key); - Long userId = user.getId(); + Long userId = userService.findByUserName(key).getId(); Payment kakaoPay = paymentService.getKakaoPayInfo(userId); @@ -97,13 +105,15 @@ public ApiResponse refund(@RequestParam("id") St } @PostMapping("/subscribe") - public ApiResponse subscribePayRequest(@AuthenticationPrincipal CustomUserDetails customUserDetails) { - User user = userService.findByUserName(customUserDetails.getUsername()); - Long userId = user.getId(); + public ApiResponse subscribePayRequest( + @AuthenticationPrincipal CustomUserDetails customUserDetails + ) { + Long userId = userService.findByUserName(customUserDetails.getUsername()).getId(); Payment kakaoPay = paymentService.getKakaoPayInfo(userId); - PaymentDto.KakaoApproveResponse kakaoApproveResponse = paymentService.approveSubscribeResponse(kakaoPay.getSid()); + PaymentDto.KakaoApproveResponse kakaoApproveResponse = paymentService.approveSubscribeResponse( + kakaoPay.getSid(), userId); paymentService.savePayInfo(userId, kakaoApproveResponse); @@ -111,13 +121,15 @@ public ApiResponse subscribePayRequest(@Authent } @PostMapping("/subscribe/key") - public ApiResponse subscribePayRequest(@RequestParam("id") String key) { - User user = userService.findByUserName(key); - Long userId = user.getId(); + public ApiResponse subscribePayRequest( + @RequestParam("id") String key + ) { + Long userId = userService.findByUserName(key).getId(); Payment kakaoPay = paymentService.getKakaoPayInfo(userId); - PaymentDto.KakaoApproveResponse kakaoApproveResponse = paymentService.approveSubscribeResponse(kakaoPay.getSid()); + PaymentDto.KakaoApproveResponse kakaoApproveResponse = paymentService.approveSubscribeResponse( + kakaoPay.getSid(), userId); paymentService.savePayInfo(userId, kakaoApproveResponse); @@ -126,73 +138,51 @@ public ApiResponse subscribePayRequest(@Request @PostMapping("/subscribe/cancel") @Operation(summary = "카카오페이 구독 취소 API", description = "카카오페이 구독을 취소하는 API 입니다.") - public ApiResponse subscribeCancelRequest(@AuthenticationPrincipal CustomUserDetails customUserDetails) { - User user = userService.findByUserName(customUserDetails.getUsername()); - Long userId = user.getId(); + public ApiResponse subscribeCancelRequest( + @AuthenticationPrincipal CustomUserDetails customUserDetails + ) { + Long userId = userService.findByUserName(customUserDetails.getUsername()).getId(); - Payment kakaoPay = paymentService.getKakaoPayInfo(userId); - - PaymentDto.KakaoSubscribeCancelResponse kakaoSubscribeCancelResponse = paymentService.subscribeCancelResponse(kakaoPay.getSid()); + PaymentDto.KakaoSubscribeCancelResponse kakaoSubscribeCancelResponse = paymentService.subscribeCancelResponse( + userId); return ApiResponse.onSuccess(SuccessCode.PAYMENT_URL_CREATE_SUCCESS, kakaoSubscribeCancelResponse); } @PostMapping("/subscribe/cancel/key") @Operation(summary = "카카오페이 구독 취소 API", description = "key를 이용하여 카카오페이 구독을 취소하는 API 입니다.") - public ApiResponse subscribeCancelRequest(@RequestParam("id") String key) { - User user = userService.findByUserName(key); - Long userId = user.getId(); + public ApiResponse subscribeCancelRequest( + @RequestParam("id") String key + ) { + Long userId = userService.findByUserName(key).getId(); - Payment kakaoPay = paymentService.getKakaoPayInfo(userId); - - PaymentDto.KakaoSubscribeCancelResponse kakaoSubscribeCancelResponse = paymentService.subscribeCancelResponse(kakaoPay.getSid()); + PaymentDto.KakaoSubscribeCancelResponse kakaoSubscribeCancelResponse = paymentService.subscribeCancelResponse( + userId); return ApiResponse.onSuccess(SuccessCode.PAYMENT_URL_CREATE_SUCCESS, kakaoSubscribeCancelResponse); } @GetMapping("/subscribe/status") @Operation(summary = "카카오페이 구독 상태 확인 API", description = "카카오페이 구독 상태를 확인하는 API 입니다.") - public ApiResponse subscribeStatusRequest(@AuthenticationPrincipal CustomUserDetails customUserDetails) { - User user = userService.findByUserName(customUserDetails.getUsername()); - Long userId = user.getId(); - - PaymentDto.KakaoSubscribeStatusResponse kakaoSubscribeStatusResponse = new PaymentDto.KakaoSubscribeStatusResponse(); + public ApiResponse subscribeStatusRequest( + @AuthenticationPrincipal CustomUserDetails customUserDetails + ) { + Long userId = userService.findByUserName(customUserDetails.getUsername()).getId(); - boolean isLogExist = false; - if (paymentService.getKakaoPayLog(userId)) { - Payment kakaoPay = paymentService.getKakaoPayInfo(userId); + PaymentDto.KakaoPayStatus kakaoPayStatus = paymentService.getSubscribeStatus(userId); - if (kakaoPay.getSid() == null || kakaoPay.getSid().isEmpty()) {} - else { - isLogExist = true; - - kakaoSubscribeStatusResponse = paymentService.subscribeStatusResponse(kakaoPay.getSid()); - } - } - - return ApiResponse.onSuccess(SuccessCode.PAYMENT_VIEW_SUBSCRIBE_STATUS_SUCCESS, PaymentConverter.toKakaoPayStatus(isLogExist, kakaoSubscribeStatusResponse)); + return ApiResponse.onSuccess(SuccessCode.PAYMENT_VIEW_SUBSCRIBE_STATUS_SUCCESS, kakaoPayStatus); } @GetMapping("/subscribe/status/key") @Operation(summary = "카카오페이 구독 상태 확인 API", description = "key를 이용하여 카카오페이 구독 상태를 확인하는 API 입니다.") - public ApiResponse subscribeStatusRequest(@RequestParam("id") String key) { - User user = userService.findByUserName(key); - Long userId = user.getId(); - - PaymentDto.KakaoSubscribeStatusResponse kakaoSubscribeStatusResponse = new PaymentDto.KakaoSubscribeStatusResponse(); - - boolean isLogExist = false; - if (paymentService.getKakaoPayLog(userId)) { - Payment kakaoPay = paymentService.getKakaoPayInfo(userId); - - if (kakaoPay.getSid() == null || kakaoPay.getSid().isEmpty()) {} - else { - isLogExist = true; + public ApiResponse subscribeStatusRequest( + @RequestParam("id") String key + ) { + Long userId = userService.findByUserName(key).getId(); - kakaoSubscribeStatusResponse = paymentService.subscribeStatusResponse(kakaoPay.getSid()); - } - } + PaymentDto.KakaoPayStatus kakaoPayStatus = paymentService.getSubscribeStatus(userId); - return ApiResponse.onSuccess(SuccessCode.PAYMENT_VIEW_SUBSCRIBE_STATUS_SUCCESS, PaymentConverter.toKakaoPayStatus(isLogExist, kakaoSubscribeStatusResponse)); + return ApiResponse.onSuccess(SuccessCode.PAYMENT_VIEW_SUBSCRIBE_STATUS_SUCCESS, kakaoPayStatus); } } diff --git a/src/main/java/com/example/silverbridgeX_user/payment/domain/Payment.java b/src/main/java/com/example/silverbridgeX_user/payment/domain/Payment.java index d5c231d..1ee58e1 100644 --- a/src/main/java/com/example/silverbridgeX_user/payment/domain/Payment.java +++ b/src/main/java/com/example/silverbridgeX_user/payment/domain/Payment.java @@ -2,8 +2,18 @@ import com.example.silverbridgeX_user.global.entity.BaseEntity; import com.example.silverbridgeX_user.user.domain.User; -import jakarta.persistence.*; -import lombok.*; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.OneToOne; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; @Entity @Getter @@ -23,12 +33,7 @@ public class Payment extends BaseEntity { @JoinColumn(name = "user_id") private User user; - public void updateTid(String tid) { this.tid = tid; } - - public void updateSid(String sid) { this.sid = sid; } - - public void updatePayInfo(String tid, String sid) { - this.tid = tid; + public void updateSid(String sid) { this.sid = sid; } } diff --git a/src/main/java/com/example/silverbridgeX_user/payment/repository/PaymentRepository.java b/src/main/java/com/example/silverbridgeX_user/payment/repository/PaymentRepository.java index 5c131c3..623f422 100644 --- a/src/main/java/com/example/silverbridgeX_user/payment/repository/PaymentRepository.java +++ b/src/main/java/com/example/silverbridgeX_user/payment/repository/PaymentRepository.java @@ -1,17 +1,23 @@ package com.example.silverbridgeX_user.payment.repository; import com.example.silverbridgeX_user.payment.domain.Payment; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; - import java.util.List; import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; public interface PaymentRepository extends JpaRepository { - boolean existsByUser_Id(Long userId); - Optional findByUser_Id(Long userId); + boolean existsByUserId(Long userId); + Optional findByTid(String tid); + + boolean existsByTid(String tid); + @Query("SELECT k FROM Payment k JOIN FETCH k.user WHERE k.sid IS NOT NULL") List findAllWithMemberAndSidNotNull(); + + @Query("SELECT p FROM Payment p WHERE p.user.id = :userId ORDER BY p.createdAt DESC") + Optional getLatestKakaoPayInfo(@Param("userId") Long userId); } diff --git a/src/main/java/com/example/silverbridgeX_user/payment/service/PaymentService.java b/src/main/java/com/example/silverbridgeX_user/payment/service/PaymentService.java index d551b99..2eefb86 100644 --- a/src/main/java/com/example/silverbridgeX_user/payment/service/PaymentService.java +++ b/src/main/java/com/example/silverbridgeX_user/payment/service/PaymentService.java @@ -2,38 +2,31 @@ import com.example.silverbridgeX_user.global.api_payload.ErrorCode; import com.example.silverbridgeX_user.global.exception.GeneralException; +import com.example.silverbridgeX_user.global.util.RestTemplateUtil; import com.example.silverbridgeX_user.payment.converter.PaymentConverter; import com.example.silverbridgeX_user.payment.domain.Payment; import com.example.silverbridgeX_user.payment.dto.PaymentDto; import com.example.silverbridgeX_user.payment.repository.PaymentRepository; import com.example.silverbridgeX_user.user.domain.User; import com.example.silverbridgeX_user.user.repository.UserRepository; -import lombok.RequiredArgsConstructor; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.scheduling.annotation.EnableScheduling; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.client.RestTemplate; - import java.time.LocalDate; -import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.HashMap; import java.util.List; import java.util.Map; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpHeaders; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @Service @RequiredArgsConstructor -@Transactional -@EnableScheduling public class PaymentService { - private RestTemplate restTemplate = new RestTemplate(); - private PaymentDto.KakaoReadyResponse kakaoReadyResponse; private final PaymentRepository paymentRepository; private final UserRepository userRepository; + private final RestTemplateUtil restTemplateUtil; @Value("${kakaopay.secret_key}") private String secretKey; @@ -41,6 +34,14 @@ public class PaymentService { @Value("${kakaopay.cid}") private String cid; + public static final String BASE_URL = "https://open-api.kakaopay.com/online/v1/payment"; + public static final String READY_URL = BASE_URL + "/ready"; + public static final String APPROVE_URL = BASE_URL + "/approve"; + public static final String CANCEL_URL = BASE_URL + "/cancel"; + public static final String SUBSCRIBE_URL = BASE_URL + "/subscription"; + public static final String SUBSCRIBE_STATUS_URL = BASE_URL + "/manage/subscription/status"; + public static final String SUBSCRIBE_CANCEL_URL = BASE_URL + "/manage/subscription/inactive"; + private HttpHeaders getHeaders() { HttpHeaders httpHeaders = new HttpHeaders(); String auth = "SECRET_KEY " + secretKey; @@ -49,46 +50,45 @@ private HttpHeaders getHeaders() { return httpHeaders; } - public PaymentDto.KakaoReadyResponse kakaoPayReady() { + public PaymentDto.KakaoReadyResponse kakaoPayReady(Long userId) { Map parameters = new HashMap<>(); - parameters.put("cid", cid); parameters.put("partner_order_id", "ORDER_ID"); - parameters.put("partner_user_id", "USER_ID"); + parameters.put("partner_user_id", String.valueOf(userId)); parameters.put("item_name", "은빛동행 구독"); parameters.put("quantity", "1"); parameters.put("total_amount", "9900"); parameters.put("vat_amount", "200"); parameters.put("tax_free_amount", "0"); - parameters.put("approval_url", "http://15.165.17.95/user/payment/success"); // http://15.165.17.95/user/payment/success http://localhost:8080/payment/success - parameters.put("fail_url", "http://15.165.17.95/user/payment/fail"); // http://15.165.17.95/user/payment/fail http://localhost:8080/payment/fail - parameters.put("cancel_url", "http://15.165.17.95/user/payment/cancel"); // http://15.165.17.95/user/payment/cancel http://localhost:8080/payment/cancel - - - HttpEntity> requestEntity = new HttpEntity<>(parameters, this.getHeaders()); - - kakaoReadyResponse = restTemplate.postForObject( - "https://open-api.kakaopay.com/online/v1/payment/ready", - requestEntity, - PaymentDto.KakaoReadyResponse.class); - return kakaoReadyResponse; + parameters.put("approval_url", "http://15.165.17.95/user/payment/success?userId=" + userId); + parameters.put("fail_url", "http://15.165.17.95/user/payment/fail"); + parameters.put("cancel_url", "http://15.165.17.95/user/payment/cancel"); + + try { + return restTemplateUtil.post(READY_URL, parameters, getHeaders(), PaymentDto.KakaoReadyResponse.class); + } catch (Exception e) { + throw new GeneralException(ErrorCode.EXTERNAL_API_ERROR); + } } - public PaymentDto.KakaoApproveResponse approveResponse(String pgToken) { + @Transactional(readOnly = true) + public PaymentDto.KakaoApproveResponse approveResponse(String pgToken, Long userId) { + Payment payment = paymentRepository.getLatestKakaoPayInfo(userId) + .orElseThrow(() -> GeneralException.of(ErrorCode.TID_NOT_EXIST)); + String tid = payment.getTid(); + Map parameters = new HashMap<>(); parameters.put("cid", cid); - parameters.put("tid", kakaoReadyResponse.getTid()); + parameters.put("tid", tid); parameters.put("partner_order_id", "ORDER_ID"); parameters.put("partner_user_id", "USER_ID"); parameters.put("pg_token", pgToken); - HttpEntity> requestEntity = new HttpEntity<>(parameters, this.getHeaders()); - - PaymentDto.KakaoApproveResponse kakaoApproveResponse = restTemplate.postForObject( - "https://open-api.kakaopay.com/online/v1/payment/approve", - requestEntity, - PaymentDto.KakaoApproveResponse.class); - return kakaoApproveResponse; + try { + return restTemplateUtil.post(APPROVE_URL, parameters, getHeaders(), PaymentDto.KakaoApproveResponse.class); + } catch (Exception e) { + throw new GeneralException(ErrorCode.PAYMENT_FAILED); + } } public PaymentDto.KakaoCancelResponse cancelResponse(String tid) { @@ -103,73 +103,15 @@ public PaymentDto.KakaoCancelResponse cancelResponse(String tid) { parameters.put("cancel_tax_free_amount", "0"); parameters.put("cancel_vat_amount", "0"); - HttpEntity> requestEntity = new HttpEntity<>(parameters, this.getHeaders()); - - PaymentDto.KakaoCancelResponse kakaoCancelResponse = restTemplate.postForObject( - "https://open-api.kakaopay.com/online/v1/payment/cancel", - requestEntity, - PaymentDto.KakaoCancelResponse.class); - return kakaoCancelResponse; - } - - public Payment getKakaoPayInfo(Long userId) { - Payment kakaoPay = paymentRepository.findByUser_Id(userId).orElseThrow(() -> new GeneralException(ErrorCode.TID_NOT_EXIST)); - - return kakaoPay; - } - - public boolean getKakaoPayLog(Long userId) { - return paymentRepository.existsByUser_Id(userId); - } - - public boolean getPremiumState(Long userId) { - boolean isPremium = false; - - if (paymentRepository.existsByUser_Id(userId)) { - Payment kakaoPay = paymentRepository.findByUser_Id(userId).orElseThrow(() -> new GeneralException(ErrorCode.TID_NOT_EXIST)); - - if (kakaoPay.getSid() == null || kakaoPay.getSid().isEmpty()) {} - else { - Map parameters = new HashMap<>(); - parameters.put("cid", cid); - parameters.put("sid", kakaoPay.getSid()); - - HttpEntity> requestEntity = new HttpEntity<>(parameters, this.getHeaders()); - - PaymentDto.KakaoSubscribeStatusResponse kakaoSubscribeStatusResponse = restTemplate.postForObject( - "https://open-api.kakaopay.com/online/v1/payment/manage/subscription/status", - requestEntity, - PaymentDto.KakaoSubscribeStatusResponse.class); - - if (kakaoSubscribeStatusResponse.getStatus().equals("ACTIVE")) { - isPremium = true; - } - else { - String last_approved_at; - if(kakaoSubscribeStatusResponse.getLast_approved_at() == null || kakaoSubscribeStatusResponse.getLast_approved_at().isEmpty()) { - last_approved_at = kakaoSubscribeStatusResponse.getCreated_at(); - } - else last_approved_at = kakaoSubscribeStatusResponse.getLast_approved_at(); - - DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME; - LocalDateTime lastApprovedAt = LocalDateTime.parse(last_approved_at, formatter); - - LocalDateTime oneMonthLater = lastApprovedAt.plusMonths(1).withHour(14).withMinute(0).withSecond(0); - - LocalDateTime now = LocalDateTime.now(); - - if (now.isBefore(oneMonthLater)) { - isPremium = true; - } - } - } - + try { + return restTemplateUtil.post(CANCEL_URL, parameters, getHeaders(), PaymentDto.KakaoCancelResponse.class); + } catch (Exception e) { + throw new GeneralException(ErrorCode.PAYMENT_FAILED); } - - return isPremium; } - public PaymentDto.KakaoApproveResponse approveSubscribeResponse(String sid) { + @Transactional + public PaymentDto.KakaoApproveResponse approveSubscribeResponse(String sid, Long userId) { if (sid == null || sid.isEmpty()) { throw new GeneralException(ErrorCode.SID_NOT_EXIST); } @@ -178,23 +120,34 @@ public PaymentDto.KakaoApproveResponse approveSubscribeResponse(String sid) { parameters.put("cid", cid); parameters.put("sid", sid); parameters.put("partner_order_id", "ORDER_ID"); - parameters.put("partner_user_id", "USER_ID"); + parameters.put("partner_user_id", String.valueOf(userId)); parameters.put("item_name", "BodyCheck 구독"); parameters.put("quantity", "1"); parameters.put("total_amount", "4900"); parameters.put("vat_amount", "200"); parameters.put("tax_free_amount", "0"); - HttpEntity> requestEntity = new HttpEntity<>(parameters, this.getHeaders()); + PaymentDto.KakaoApproveResponse response; + try { + response = restTemplateUtil.post(SUBSCRIBE_URL, parameters, getHeaders(), + PaymentDto.KakaoApproveResponse.class); + } catch (Exception e) { + throw new GeneralException(ErrorCode.PAYMENT_FAILED); + } + + User user = userRepository.findById(userId) + .orElseThrow(() -> new GeneralException(ErrorCode.USER_NOT_FOUND)); + user.enableSubscription(); - PaymentDto.KakaoApproveResponse kakaoApproveResponse = restTemplate.postForObject( - "https://open-api.kakaopay.com/online/v1/payment/subscription", - requestEntity, - PaymentDto.KakaoApproveResponse.class); - return kakaoApproveResponse; + return response; } - public PaymentDto.KakaoSubscribeCancelResponse subscribeCancelResponse(String sid) { + @Transactional + public PaymentDto.KakaoSubscribeCancelResponse subscribeCancelResponse(Long userId) { + Payment kakaoPay = paymentRepository.getLatestKakaoPayInfo(userId) + .orElseThrow(() -> new GeneralException(ErrorCode.TID_NOT_EXIST)); + + String sid = kakaoPay.getSid(); if (sid == null || sid.isEmpty()) { throw new GeneralException(ErrorCode.SID_NOT_EXIST); } @@ -203,13 +156,20 @@ public PaymentDto.KakaoSubscribeCancelResponse subscribeCancelResponse(String si parameters.put("cid", cid); parameters.put("sid", sid); - HttpEntity> requestEntity = new HttpEntity<>(parameters, this.getHeaders()); + PaymentDto.KakaoSubscribeCancelResponse response; + try { + response = restTemplateUtil.post(SUBSCRIBE_CANCEL_URL, parameters, + getHeaders(), + PaymentDto.KakaoSubscribeCancelResponse.class); + } catch (Exception e) { + throw new GeneralException(ErrorCode.EXTERNAL_API_ERROR); + } + + User user = userRepository.findById(userId) + .orElseThrow(() -> new GeneralException(ErrorCode.USER_NOT_FOUND)); + user.disableSubscription(); - PaymentDto.KakaoSubscribeCancelResponse kakaoSubscribeCancelResponse = restTemplate.postForObject( - "https://open-api.kakaopay.com/online/v1/payment/manage/subscription/inactive", - requestEntity, - PaymentDto.KakaoSubscribeCancelResponse.class); - return kakaoSubscribeCancelResponse; + return response; } public PaymentDto.KakaoSubscribeStatusResponse subscribeStatusResponse(String sid) { @@ -221,94 +181,127 @@ public PaymentDto.KakaoSubscribeStatusResponse subscribeStatusResponse(String si parameters.put("cid", cid); parameters.put("sid", sid); - HttpEntity> requestEntity = new HttpEntity<>(parameters, this.getHeaders()); + try { + return restTemplateUtil.post(SUBSCRIBE_STATUS_URL, parameters, getHeaders(), + PaymentDto.KakaoSubscribeStatusResponse.class); + } catch (Exception e) { + throw new GeneralException(ErrorCode.EXTERNAL_API_ERROR); + } + } - PaymentDto.KakaoSubscribeStatusResponse kakaoSubscribeStatusResponse = restTemplate.postForObject( - "https://open-api.kakaopay.com/online/v1/payment/manage/subscription/status", - requestEntity, - PaymentDto.KakaoSubscribeStatusResponse.class); - return kakaoSubscribeStatusResponse; + @Transactional(readOnly = true) + public Payment getKakaoPayInfo(Long userId) { + Payment kakaoPay = paymentRepository.getLatestKakaoPayInfo(userId) + .orElseThrow(() -> new GeneralException(ErrorCode.TID_NOT_EXIST)); + + return kakaoPay; } + @Transactional public void saveTid(Long userId, String tid) { - Payment kakaoPay; - if (paymentRepository.existsByUser_Id(userId)) { - kakaoPay = paymentRepository.findByUser_Id(userId).orElseThrow(() -> new GeneralException(ErrorCode.TID_SID_UNSUPPORTED)); - kakaoPay.updateTid(tid); - } - else { - User user = userRepository.findById(userId).orElseThrow(() -> new GeneralException(ErrorCode.USER_NOT_FOUND)); - kakaoPay = PaymentConverter.toKakaoPayTid(tid, user); + if (paymentRepository.existsByTid(tid)) { + throw new GeneralException(ErrorCode.TID_ALREADY_EXIST); } + + User user = userRepository.findById(userId) + .orElseThrow(() -> new GeneralException(ErrorCode.USER_NOT_FOUND)); + + Payment kakaoPay = PaymentConverter.toKakaoPayTid(tid, user); + paymentRepository.save(kakaoPay); } + @Transactional public void saveSid(PaymentDto.KakaoApproveResponse kakaoApproveResponse) { - Payment kakaoPay = paymentRepository.findByTid(kakaoApproveResponse.getTid()).orElseThrow(() -> new GeneralException(ErrorCode.TID_NOT_EXIST)); + Payment kakaoPay = paymentRepository.findByTid(kakaoApproveResponse.getTid()) + .orElseThrow(() -> new GeneralException(ErrorCode.TID_NOT_EXIST)); kakaoPay.updateSid(kakaoApproveResponse.getSid()); paymentRepository.save(kakaoPay); } + @Transactional public void savePayInfo(Long userId, PaymentDto.KakaoApproveResponse kakaoApproveResponse) { - Payment kakaoPay; - if (paymentRepository.existsByUser_Id(userId)) { - kakaoPay = paymentRepository.findByUser_Id(userId).orElseThrow(() -> new GeneralException(ErrorCode.TID_SID_UNSUPPORTED)); - kakaoPay.updatePayInfo(kakaoApproveResponse.getTid(), kakaoApproveResponse.getSid()); - } - else { - User member = userRepository.findById(userId).orElseThrow(() -> new GeneralException(ErrorCode.USER_NOT_FOUND)); - kakaoPay = PaymentConverter.toKakaoPay(kakaoApproveResponse.getTid(), kakaoApproveResponse.getSid(), member); - } + User member = userRepository.findById(userId) + .orElseThrow(() -> new GeneralException(ErrorCode.USER_NOT_FOUND)); + Payment kakaoPay = PaymentConverter.toKakaoPay(kakaoApproveResponse.getTid(), kakaoApproveResponse.getSid(), + member); + paymentRepository.save(kakaoPay); } + @Transactional public void cancelPay(Long userId) { - Payment kakaoPay = paymentRepository.findByUser_Id(userId).orElseThrow(() -> new GeneralException(ErrorCode.TID_NOT_EXIST)); + Payment kakaoPay = paymentRepository.getLatestKakaoPayInfo(userId) + .orElseThrow(() -> new GeneralException(ErrorCode.TID_NOT_EXIST)); paymentRepository.delete(kakaoPay); } + @Transactional(readOnly = true) + public PaymentDto.KakaoPayStatus getSubscribeStatus(Long userId) { + User user = userRepository.findById(userId) + .orElseThrow(() -> new GeneralException(ErrorCode.USER_NOT_FOUND)); + + if (!user.isSubscribeActive()) { + return PaymentConverter.toKakaoPayStatus(false, new PaymentDto.KakaoSubscribeStatusResponse()); + } + + Payment kakaoPay = getKakaoPayInfo(userId); + + if (kakaoPay.getSid() != null && !kakaoPay.getSid().isEmpty()) { + PaymentDto.KakaoSubscribeStatusResponse kakaoSubscribeStatusResponse = subscribeStatusResponse( + kakaoPay.getSid()); + return PaymentConverter.toKakaoPayStatus(true, kakaoSubscribeStatusResponse); + } + + return PaymentConverter.toKakaoPayStatus(false, new PaymentDto.KakaoSubscribeStatusResponse()); + } + + @Transactional public void regularPayment() { List kakaoPayList = paymentRepository.findAllWithMemberAndSidNotNull(); kakaoPayList.stream() .forEach(kakaoPay -> { if (kakaoPay.getSid() != null && !kakaoPay.getSid().isEmpty()) { - PaymentDto.KakaoSubscribeStatusResponse kakaoSubscribeStatusResponse = subscribeStatusResponse(kakaoPay.getSid()); + PaymentDto.KakaoSubscribeStatusResponse kakaoSubscribeStatusResponse = subscribeStatusResponse( + kakaoPay.getSid()); + + // 사용자가 카카오 결제 내역 화면에서 직접 해지 요청을 한 경우, db에 대항 상황이 반영x + // -> 결제 상태를 우리 DB에도 반영 + if (kakaoSubscribeStatusResponse.getStatus().equals("INACTIVE")) { + kakaoPay.getUser().disableSubscription(); + return; // 결제 해지된 유저는 패스 + } - // "ACTIVE" 상태인지 확인 if (kakaoSubscribeStatusResponse.getStatus().equals("ACTIVE")) { String lastApprovedAtStr = kakaoSubscribeStatusResponse.getLast_approved_at(); if (lastApprovedAtStr == null || lastApprovedAtStr.isEmpty()) { lastApprovedAtStr = kakaoSubscribeStatusResponse.getCreated_at(); } - // last_approved_at을 LocalDate로 변환 DateTimeFormatter formatter = DateTimeFormatter.ISO_DATE_TIME; LocalDate lastApprovedAt = LocalDate.parse(lastApprovedAtStr, formatter); LocalDate today = LocalDate.now(); - // 결제일과 오늘의 일(day)이 같고, 마지막 결제일이 이번 달이 아닌 경우에만 결제 수행 + // 결제일 + 새로운 달이 되었을 때 결제 요청 if (today.getDayOfMonth() == lastApprovedAt.getDayOfMonth() && - (today.getYear() != lastApprovedAt.getYear() || today.getMonthValue() != lastApprovedAt.getMonthValue())) { + (today.getYear() != lastApprovedAt.getYear() + || today.getMonthValue() != lastApprovedAt.getMonthValue())) { - PaymentDto.KakaoApproveResponse approveResponse = approveSubscribeResponse(kakaoPay.getSid()); + PaymentDto.KakaoApproveResponse approveResponse = approveSubscribeResponse( + kakaoPay.getSid(), kakaoPay.getUser().getId()); savePayInfo(kakaoPay.getUser().getId(), approveResponse); } -// if (today.getDayOfMonth() == lastApprovedAt.getDayOfMonth()) { -// KakaoPayDTO.KakaoApproveResponse approveResponse = approveSubscribeResponse(kakaoPay.getSid()); -// -// savePayInfo(kakaoPay.getMember().getId(), approveResponse); -// } } } }); - -// System.out.println("정기 결제 작업 완료"); } + + } diff --git a/src/main/java/com/example/silverbridgeX_user/user/converter/UserConverter.java b/src/main/java/com/example/silverbridgeX_user/user/converter/UserConverter.java index cd1aab4..defc0da 100644 --- a/src/main/java/com/example/silverbridgeX_user/user/converter/UserConverter.java +++ b/src/main/java/com/example/silverbridgeX_user/user/converter/UserConverter.java @@ -18,6 +18,7 @@ public static User saveUser(UserRequestDto.UserSigInReqDto userReqDto, String ke .username(key) .nickname(userReqDto.getNickname()) .streetAddress(userReqDto.getStreetAddress()) + .isSubscribed(false) .build(); } diff --git a/src/main/java/com/example/silverbridgeX_user/user/domain/User.java b/src/main/java/com/example/silverbridgeX_user/user/domain/User.java index 37c5aac..b210b55 100644 --- a/src/main/java/com/example/silverbridgeX_user/user/domain/User.java +++ b/src/main/java/com/example/silverbridgeX_user/user/domain/User.java @@ -58,6 +58,8 @@ public class User extends BaseEntity { @OneToMany(mappedBy = "guardian", cascade = CascadeType.ALL) private List olders = new ArrayList<>(); + private Boolean isSubscribed; + private String sex; private LocalDate birth; @@ -117,6 +119,18 @@ public void updateCoordinate(String longitude, String latitude) { public void updateGuardian(User guardian) { this.guardian = guardian; // null 가능 } + + public Boolean isSubscribeActive() { + return isSubscribed; + } + + public void enableSubscription() { + this.isSubscribed = true; + } + + public void disableSubscription() { + this.isSubscribed = false; + } }