diff --git a/src/main/java/co/orange/ddanzi/domain/user/enums/FcmCase.java b/src/main/java/co/orange/ddanzi/domain/user/enums/FcmCase.java index 19f6350f..ca4150ad 100644 --- a/src/main/java/co/orange/ddanzi/domain/user/enums/FcmCase.java +++ b/src/main/java/co/orange/ddanzi/domain/user/enums/FcmCase.java @@ -18,10 +18,10 @@ public enum FcmCase { B4("상품 배송에 문제가 생겼나요?","24시간 후에 자동 구매확정 됩니다."), //관리자 - C1("관리자 알림: 회원가입", "새로운 유저가 등록되었습니다."), - C2("관리자 알림: 구매실행", "결제가 실행되었습니다."), - C3("관리자 알림: 환불실행", "중복 결제로 인해 환불되었습니다."), - C4("관리자 알림: 환불실행", "판매자가 상품을 판매하여 결제내역이 환불되었습니다.") + C1("⚠️관리자 알림: 회원가입", "새로운 유저가 등록되었습니다."), + C2("⚠️관리자 알림: 구매실행", "결제가 실행되었습니다."), + C3("⚠️관리자 알림: 환불실행", "중복 결제로 인해 환불되었습니다."), + C4("⚠️관리자 알림: 환불실행", "판매자가 상품을 판매하여 결제내역이 환불되었습니다."), ; private final String title; diff --git a/src/main/java/co/orange/ddanzi/dto/payment/PortOneTokenRequestDto.java b/src/main/java/co/orange/ddanzi/dto/payment/PortOneTokenRequestDto.java index b8e6f458..2cbfa578 100644 --- a/src/main/java/co/orange/ddanzi/dto/payment/PortOneTokenRequestDto.java +++ b/src/main/java/co/orange/ddanzi/dto/payment/PortOneTokenRequestDto.java @@ -1,15 +1,11 @@ package co.orange.ddanzi.dto.payment; -import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Builder; import lombok.Getter; @Builder @Getter public class PortOneTokenRequestDto { - @JsonProperty("imp_key") - private String impKey; // 필드 이름을 camelCase로 수정 - - @JsonProperty("imp_secret") - private String impSecret; + private String imp_key; + private String imp_secret; } diff --git a/src/main/java/co/orange/ddanzi/global/firebase/FirebaseUtils.java b/src/main/java/co/orange/ddanzi/global/firebase/FirebaseUtils.java index caff8fd4..987a77e9 100644 --- a/src/main/java/co/orange/ddanzi/global/firebase/FirebaseUtils.java +++ b/src/main/java/co/orange/ddanzi/global/firebase/FirebaseUtils.java @@ -29,6 +29,20 @@ public String sendMessage(String fcmToken, FcmCase fcmCase) { } } + public String sendAdminMessage(String fcmToken, String title, String body) { + try { + log.info("관리자 Sending FCM message: {}", title); + return FirebaseMessaging.getInstance().send(makeAdminMessage(fcmToken , title, body)); + } catch (FirebaseMessagingException e) { + log.error("관리자 FirebaseMessagingException occurred: {}", e.getMessage()); + return "관리자 FCM 메시지 전송 실패: " + e.getMessage(); + } catch (Exception e) { + log.info(e.getMessage()); + return e.getMessage(); + } + } + + public Message makeMessage(String fcmSendDto, FcmCase fcmCase) { Notification notification = Notification .builder() @@ -44,6 +58,22 @@ public Message makeMessage(String fcmSendDto, FcmCase fcmCase) { .build(); } + public Message makeAdminMessage(String fcmSendDto, String title, String body) { + Notification notification = Notification + .builder() + .setTitle(title) + .setBody(body) + .build(); + return Message + .builder() + .setNotification(notification) + .putData("title", title) + .putData("body",body) + .setToken(fcmSendDto) + .build(); + } + + public MulticastMessage makeMessages(List targetTokens, String title, String body) { Notification notification = Notification.builder() .setTitle(title) diff --git a/src/main/java/co/orange/ddanzi/repository/ItemRepository.java b/src/main/java/co/orange/ddanzi/repository/ItemRepository.java index b9d7c867..0bb5561b 100644 --- a/src/main/java/co/orange/ddanzi/repository/ItemRepository.java +++ b/src/main/java/co/orange/ddanzi/repository/ItemRepository.java @@ -12,9 +12,9 @@ import java.util.Optional; public interface ItemRepository extends JpaRepository { - List findAllBySeller(User user); + @Query("SELECT i FROM Item i WHERE i.seller = :user AND i.status <> 'DELETED'") + List findAllBySellerAndNotDeleted(User user); - Integer countAllBySeller(User user); @Query("SELECT MAX(CAST(RIGHT(i.id, 2) AS integer)) FROM Item i WHERE i.product = :product") Integer findMaxSequenceNumberByProduct(@Param("product") Product product); diff --git a/src/main/java/co/orange/ddanzi/service/ItemService.java b/src/main/java/co/orange/ddanzi/service/ItemService.java index c079780b..1343f7f2 100644 --- a/src/main/java/co/orange/ddanzi/service/ItemService.java +++ b/src/main/java/co/orange/ddanzi/service/ItemService.java @@ -138,31 +138,39 @@ public ApiResponse getItem(String itemId){ public ApiResponse deleteItem(String itemId){ User user = authUtils.getUser(); Item item = itemRepository.findById(itemId).orElseThrow(ItemNotFoundException::new); + if(!item.getSeller().equals(user)) return ApiResponse.onFailure(Error.ITEM_UNAUTHORIZED_USER, null); Order order = orderRepository.findByItemAndStatus(item).orElse(null); - if(order==null){ - log.info("거래 취소 중 - 거래중이지 않아 바로 제품을 삭제합니다."); - item.updateStatus(ItemStatus.DELETED); - } - else{ + if(order!=null){ log.info("거래 취소 중 - 거래중인 상품이 있습니다. 대안을 탐색합니다."); Item newItem = itemRepository.findNearestExpiryItem(item.getProduct()).orElse(null); if(newItem == null) { - log.info("환불을 진행합니다."); + log.info("대안이 없음 - 거래를 취소하고 환불을 진행합니다."); Payment payment = paymentRepository.findByOrder(order); User buyer = order.getBuyer(); try { paymentService.refundPayment(buyer, order, "현재 남은 재고가 없어 고객에게 결제 금액 환불합니다."); payment.updatePaymentStatusAndEndedAt(PayStatus.CANCELLED); - historyService.createPaymentHistoryWithError(buyer, payment, "재퓸 삭제- 환불 처리 성공"); + historyService.createPaymentHistoryWithError(buyer, payment, "제품 삭제- 환불 처리 성공"); }catch (Exception e){ + log.info("환불이 불가능하여 제품 삭제에 실패했습니다."); historyService.createPaymentHistoryWithError(buyer, payment, "제품 삭제 - 환불 처리 실패"); - return ApiResponse.onFailure(Error.REFUND_FAILED, Map.of("orderId", order.getId())); + return ApiResponse.onFailure(Error.REFUND_FAILED, Map.of("itemId", item.getId())); } } + else{ + log.info("대안이 있음 - 거래에 새로운 제품을 할당합니다."); + order.updateItem(newItem); + } } + log.info("제품을 삭제합니다."); + item.updateStatus(ItemStatus.DELETED); + log.info("재고를 감소시킵니다."); + Product product = item.getProduct(); + product.updateStock(product.getStock() - 1); + return ApiResponse.onSuccess(Success.DELETE_ITEM_SUCCESS, true); } @@ -219,7 +227,7 @@ private List setSelectedOptionList(Order order){ } public List getMyItemList(User user){ - List itemList = itemRepository.findAllBySeller(user); + List itemList = itemRepository.findAllBySellerAndNotDeleted(user); List myItemList = new ArrayList<>(); for(Item item : itemList){ Product product = item.getProduct(); diff --git a/src/main/java/co/orange/ddanzi/service/OrderService.java b/src/main/java/co/orange/ddanzi/service/OrderService.java index 9354735a..f77c813a 100644 --- a/src/main/java/co/orange/ddanzi/service/OrderService.java +++ b/src/main/java/co/orange/ddanzi/service/OrderService.java @@ -176,10 +176,10 @@ public void checkOrderPlacedOrder(){ LocalDateTime oneDayLimit = LocalDateTime.now().minusMinutes(1); List orderPlaceOrders = orderRepository.findOverLimitTimeOrders(OrderStatus.ORDER_PLACE, oneDayLimit); for(Order order : orderPlaceOrders){ - fcmService.sendMessageToUser(order.getItem().getSeller(), FcmCase.A2, order); - fcmService.sendMessageToUser(order.getBuyer(), FcmCase.B1, order); paymentService.refundPayment(order.getBuyer(), order, "고객이 판매확정을 하지 않아 거래가 취소되어 결제 금액을 환불합니다."); order.updateStatus(OrderStatus.CANCELLED); + fcmService.sendMessageToUser(order.getItem().getSeller(), FcmCase.A2, order); + fcmService.sendMessageToUser(order.getBuyer(), FcmCase.B1, order); } } diff --git a/src/main/java/co/orange/ddanzi/service/PaymentService.java b/src/main/java/co/orange/ddanzi/service/PaymentService.java index a4a08cd5..8bc4346a 100644 --- a/src/main/java/co/orange/ddanzi/service/PaymentService.java +++ b/src/main/java/co/orange/ddanzi/service/PaymentService.java @@ -14,7 +14,6 @@ import co.orange.ddanzi.domain.product.Product; import co.orange.ddanzi.domain.product.enums.ItemStatus; import co.orange.ddanzi.domain.user.User; -import co.orange.ddanzi.domain.user.enums.FcmCase; import co.orange.ddanzi.dto.payment.*; import co.orange.ddanzi.global.jwt.AuthUtils; import co.orange.ddanzi.repository.*; @@ -126,7 +125,7 @@ else if(payment.getPayStatus().equals(PayStatus.PAID)){ log.info("Payment is paid!!"); item.updateStatus(ItemStatus.CLOSED); product.updateStock(product.getStock() - 1); - fcmService.sendMessageToAdmin(FcmCase.C2); + fcmService.sendMessageToAdmins("⚠️관리자 알림: 구매실행", "결제가 실행되었습니다. orderId:" + order.getId()); } historyService.createPaymentHistory(buyer, payment); @@ -168,8 +167,8 @@ public String getPortOneAccessToken(){ headers.set("Content-Type", "application/json"); PortOneTokenRequestDto requestBody = PortOneTokenRequestDto.builder() - .impKey(accessKey) - .impSecret(accessSecret) + .imp_key(accessKey) + .imp_secret(accessSecret) .build(); HttpEntity entity = new HttpEntity<>(requestBody, headers); @@ -183,28 +182,35 @@ public String getPortOneAccessToken(){ public void refundPayment(User user, Order order, String reason){ if(!user.equals(order.getBuyer())) throw new RuntimeException("결제자와 요청자가 다르므로 환불이 어렵습니다."); - String baseUrl = "https://api.iamport.kr/payments/cancel"; - String url = UriComponentsBuilder.fromUriString(baseUrl) - .toUriString(); - log.info("결제 취소 url 생성, url-> {}", url); - - String key = getPortOneAccessToken(); - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_JSON); - headers.set("Authorization", key); - RefundRequestDto requestDto = RefundRequestDto.builder() - .merchant_uid(order.getId()) - .reason(reason) - .build(); - - HttpEntity entity = new HttpEntity<>(requestDto, headers); - log.info("헤더 및 request body 생성"); + try{ + String baseUrl = "https://api.iamport.kr/payments/cancel"; + String url = UriComponentsBuilder.fromUriString(baseUrl) + .toUriString(); + log.info("결제 취소 url 생성, url-> {}", url); + + String key = getPortOneAccessToken(); + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.set("Authorization", key); + + RefundRequestDto requestDto = RefundRequestDto.builder() + .merchant_uid(order.getId()) + .reason(reason) + .build(); + + HttpEntity entity = new HttpEntity<>(requestDto, headers); + log.info("헤더 및 request body 생성"); + + RestTemplate restTemplate = new RestTemplate(); + restTemplate.postForObject(url, entity, String.class); + log.info("결제 취소 api 호출"); + fcmService.sendMessageToAdmins("⚠️관리자 알림: 환불실행", "중복 결제로 인해 환불되었습니다. orderId:" + order.getId()); + }catch (Exception e){ + log.info("환불 실패"); + fcmService.sendMessageToAdmins("⚠️관리자 알림: 환불 실패", "환불에 실패했습니다. orderId:" + order.getId()); + } - RestTemplate restTemplate = new RestTemplate(); - restTemplate.postForObject(url, entity, String.class); - log.info("결제 취소 api 호출"); - fcmService.sendMessageToAdmin(FcmCase.C3); } } diff --git a/src/main/java/co/orange/ddanzi/service/auth/OAuthService.java b/src/main/java/co/orange/ddanzi/service/auth/OAuthService.java index 2f98746a..ee4b4bbf 100644 --- a/src/main/java/co/orange/ddanzi/service/auth/OAuthService.java +++ b/src/main/java/co/orange/ddanzi/service/auth/OAuthService.java @@ -1,7 +1,6 @@ package co.orange.ddanzi.service.auth; import co.orange.ddanzi.domain.user.User; -import co.orange.ddanzi.domain.user.enums.FcmCase; import co.orange.ddanzi.domain.user.enums.LoginType; import co.orange.ddanzi.domain.user.enums.UserStatus; import co.orange.ddanzi.dto.auth.SigninRequestDto; @@ -69,7 +68,7 @@ public User kakaoSignIn(SigninRequestDto requestDto) throws JsonProcessingExcept User user = userRepository.findByEmail(email).orElse(null); if (user == null){ - fcmService.sendMessageToAdmin(FcmCase.C1); + fcmService.sendMessageToAdmins("⚠️관리자 알림: 카카오 회원가입", "새로운 유저가 등록되었습니다. 총 유저 수: " + userRepository.count()); return kakaoSignUp(email); } return user; @@ -132,6 +131,7 @@ public User appleSignin(SigninRequestDto requestDto) throws JsonProcessingExcept User user = userRepository.findByEmail(email).orElse(null); if (user == null) { log.info("애플 회원가입 시작"); + fcmService.sendMessageToAdmins("⚠️관리자 알림: 애플 회원가입", "새로운 유저가 등록되었습니다. 총 유저 수: " + userRepository.count()); return appleSignup(email); } return user; @@ -178,21 +178,6 @@ public String getAppleEmail(String authorizationCode) { return payload.getEmail(); } -// private String generateClientSecret() { -// -// LocalDateTime expiration = LocalDateTime.now().plusMinutes(5); -// -// return Jwts.builder() -// .setHeaderParam(JwsHeader.KEY_ID, appleProperties.getKeyId()) -// .setIssuer(appleProperties.getTeamId()) -// .setAudience(appleProperties.getAudience()) -// .setSubject(appleProperties.getClientId()) -// .setExpiration(Date.from(expiration.atZone(ZoneId.systemDefault()).toInstant())) -// .setIssuedAt(new Date()) -// .signWith(getPrivateKey(), SignatureAlgorithm.ES256) -// .compact(); -// } - public String createClientSecret() { JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.ES256).keyID(appleProperties.getKeyId()).build(); @@ -239,21 +224,6 @@ public byte[] readPrivateKey() { return content; } -// private PrivateKey getPrivateKey() { -// Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); -// JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC"); -// -// try { -// byte[] privateKeyBytes = Base64.getDecoder().decode(appleProperties.getPrivateKey()); -// -// PrivateKeyInfo privateKeyInfo = PrivateKeyInfo.getInstance(privateKeyBytes); -// return converter.getPrivateKey(privateKeyInfo); -// } catch (Exception e) { -// throw new RuntimeException("Error converting private key from String", e); -// } -// } - - // TokenDecoder 메소드를 GetMemberInfoService 내부에 통합 private T decodePayload(String token, Class targetClass) { String[] tokenParts = token.split("\\."); diff --git a/src/main/java/co/orange/ddanzi/service/common/FcmService.java b/src/main/java/co/orange/ddanzi/service/common/FcmService.java index b005aca6..24e70451 100644 --- a/src/main/java/co/orange/ddanzi/service/common/FcmService.java +++ b/src/main/java/co/orange/ddanzi/service/common/FcmService.java @@ -85,18 +85,27 @@ public boolean sendMessageToUser(User user, FcmCase fcmCase, Order order) { return true; } - public boolean sendMessageToAdmin(FcmCase fcmCase) { - log.info("Sending FCM message to admin: {}", fcmCase.getTitle()); + public boolean sendMessageToAdmin(User user, String title, String body) { + PushAlarm pushAlarm = pushAlarmRepository.findByUser(user).orElse(null); + if(pushAlarm == null) + return false; + String fcmToken = pushAlarm.getFcmToken(); + firebaseUtils.sendAdminMessage(fcmToken, title, body); + alarmService.createAlarm(user, FcmCase.C1, null); + return true; + } + + public boolean sendMessageToAdmins(String title, String body) { + log.info("Sending FCM message to admins: {}", title); User sj = userRepository.findByEmail("tmdwns0527@naver.com").orElse(null); User jh = userRepository.findByEmail("yaksh203@naver.com").orElse(null); User sh = userRepository.findByEmail("mam07065@naver.com").orElse(null); User ys = userRepository.findByEmail("kyssa@daum.net").orElse(null); - sendMessageToUser(sj, fcmCase, null); - sendMessageToUser(jh, fcmCase, null); - sendMessageToUser(sh, fcmCase, null); - sendMessageToUser(ys, fcmCase, null); + sendMessageToAdmin(sj, title, body); + sendMessageToAdmin(jh, title, body); + sendMessageToAdmin(sh, title, body); + sendMessageToAdmin(ys, title, body); return true; } - }