diff --git a/build.gradle b/build.gradle index e1ffda78..1008b24e 100644 --- a/build.gradle +++ b/build.gradle @@ -2,6 +2,8 @@ plugins { id 'java' id 'org.springframework.boot' version '3.3.1' id 'io.spring.dependency-management' version '1.1.5' + id 'com.apollographql.apollo3' version '4.0.0-beta.7' + } group = 'camp.nextstep.edu' diff --git a/src/main/java/poomasi/domain/auth/config/SecurityConfig.java b/src/main/java/poomasi/domain/auth/config/SecurityConfig.java index f6d7f746..14acee13 100644 --- a/src/main/java/poomasi/domain/auth/config/SecurityConfig.java +++ b/src/main/java/poomasi/domain/auth/config/SecurityConfig.java @@ -39,7 +39,6 @@ public class SecurityConfig { private final CustomSuccessHandler customSuccessHandler; private final UserDetailsServiceImpl userDetailsService; - @Autowired private OAuth2UserDetailServiceImpl oAuth2UserDetailServiceImpl; @@ -74,7 +73,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti // 기본 경로 및 테스트 경로 http.authorizeHttpRequests((authorize) -> authorize - .requestMatchers(HttpMethod.GET, "/api/farm/**").permitAll() + .requestMatchers(HttpMethod.POST, "/api/farm/**").permitAll() .requestMatchers(HttpMethod.GET, "/api/product/**").permitAll() .requestMatchers(HttpMethod.GET, "/api/review/**").permitAll() .requestMatchers(HttpMethod.GET, "/health").permitAll() diff --git a/src/main/java/poomasi/domain/farm/entity/Farm.java b/src/main/java/poomasi/domain/farm/entity/Farm.java index 1b4921f5..22c7b6c1 100644 --- a/src/main/java/poomasi/domain/farm/entity/Farm.java +++ b/src/main/java/poomasi/domain/farm/entity/Farm.java @@ -17,6 +17,7 @@ import java.time.LocalDateTime; +import poomasi.domain.order.entity._farm.OrderedFarm; import poomasi.domain.review.entity.Review; @Entity @@ -77,7 +78,11 @@ public class Farm { @OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true) @JoinColumn(name = "entityId") - List reviewList = new ArrayList<>(); + private List reviewList = new ArrayList<>(); + + @OneToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "ordered_farm_id") + private OrderedFarm orderedFarm; @Builder public Farm(Long id, String name, Long ownerId, String address, String addressDetail, Double latitude, Double longitude, String description, int experiencePrice, Integer maxCapacity, Integer maxReservation) { diff --git a/src/main/java/poomasi/domain/member/entity/Member.java b/src/main/java/poomasi/domain/member/entity/Member.java index a18c99c2..2292127a 100644 --- a/src/main/java/poomasi/domain/member/entity/Member.java +++ b/src/main/java/poomasi/domain/member/entity/Member.java @@ -7,7 +7,10 @@ import lombok.NoArgsConstructor; import lombok.Setter; import org.hibernate.annotations.SQLDelete; -import poomasi.domain.order.entity.Order; +import poomasi.domain.store.entity.Store; +import poomasi.domain.member._profile.entity.MemberProfile; +import poomasi.domain.store.entity.Store; +import poomasi.domain.order.entity._product.ProductOrder; import poomasi.domain.store.entity.Store; import poomasi.domain.member._profile.entity.MemberProfile; import poomasi.domain.store.entity.Store; @@ -59,7 +62,7 @@ public class Member { private LocalDateTime deletedAt; @OneToMany(mappedBy = "member", cascade = CascadeType.ALL, orphanRemoval = true) - private List orderLists; + private List productOrderLists; @Setter @Column(nullable = true) @@ -77,6 +80,11 @@ public Member(String name, String email, String password, LoginType loginType, R this.role = role; } + public Member(String email, Role role) { + this.email = email; + this.role = role; + } + public void setMemberProfile(MemberProfile memberProfile) { this.memberProfile = memberProfile; if (memberProfile != null) { diff --git a/src/main/java/poomasi/domain/order/_aftersales/controller/CancelController.java b/src/main/java/poomasi/domain/order/_aftersales/controller/CancelController.java new file mode 100644 index 00000000..9691382a --- /dev/null +++ b/src/main/java/poomasi/domain/order/_aftersales/controller/CancelController.java @@ -0,0 +1,8 @@ +package poomasi.domain.order._aftersales.controller; + + +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class CancelController { +} diff --git a/src/main/java/poomasi/domain/order/_aftersales/controller/ExchangeController.java b/src/main/java/poomasi/domain/order/_aftersales/controller/ExchangeController.java new file mode 100644 index 00000000..7d22e7fd --- /dev/null +++ b/src/main/java/poomasi/domain/order/_aftersales/controller/ExchangeController.java @@ -0,0 +1,8 @@ +package poomasi.domain.order._aftersales.controller; + + +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class ExchangeController { +} diff --git a/src/main/java/poomasi/domain/order/_aftersales/controller/RefundController.java b/src/main/java/poomasi/domain/order/_aftersales/controller/RefundController.java new file mode 100644 index 00000000..8226cec4 --- /dev/null +++ b/src/main/java/poomasi/domain/order/_aftersales/controller/RefundController.java @@ -0,0 +1,42 @@ +package poomasi.domain.order._aftersales.controller; + + +import lombok.RequiredArgsConstructor; +import org.springframework.security.access.annotation.Secured; +import org.springframework.web.bind.annotation.*; +import poomasi.domain.order._aftersales.dto.FullRefundRequest; +import poomasi.domain.order._aftersales.dto.PartialRefundRequest; +import poomasi.domain.order._aftersales.service.RefundService; + +@RestController +@RequestMapping("/api/refund") +@RequiredArgsConstructor +public class RefundController { + + private final RefundService refundService; + + + @Secured({"ROLE_CUSTOMER", "ROLE_FARMER"}) + @GetMapping("/{refundId}") + public void getRefund(@PathVariable("refundId") Long refundId) { + + } + + @Secured({"ROLE_CUSTOMER", "ROLE_FARMER"}) + @PostMapping("/{orderProductDetailsId}") + public void processFullRefund (@PathVariable("orderProductDetailsId") Long orderProductDetailsId, + @RequestBody FullRefundRequest fullRefundRequest) { + //TODO : order product details 내부 메서드 보고 + //TODO : 환불 가능하지 받아 와야 함 + } + + + @Secured({"ROLE_CUSTOMER", "ROLE_FARMER"}) + @PostMapping("/api/refund/{orderProductDetailsId}") + public void processPartialRefund (@PathVariable("orderProductDetailsId") Long orderProductDetailsId, + @RequestBody PartialRefundRequest partialRefundRequest) { + + + + } +} diff --git a/src/main/java/poomasi/domain/order/_aftersales/dto/FullRefundRequest.java b/src/main/java/poomasi/domain/order/_aftersales/dto/FullRefundRequest.java new file mode 100644 index 00000000..d27e4423 --- /dev/null +++ b/src/main/java/poomasi/domain/order/_aftersales/dto/FullRefundRequest.java @@ -0,0 +1,6 @@ +package poomasi.domain.order._aftersales.dto; + +public record FullRefundRequest( + String refundReason +) { +} diff --git a/src/main/java/poomasi/domain/order/_aftersales/dto/PartialRefundRequest.java b/src/main/java/poomasi/domain/order/_aftersales/dto/PartialRefundRequest.java new file mode 100644 index 00000000..faf155e2 --- /dev/null +++ b/src/main/java/poomasi/domain/order/_aftersales/dto/PartialRefundRequest.java @@ -0,0 +1,9 @@ +package poomasi.domain.order._aftersales.dto; + +import java.math.BigDecimal; + +public record PartialRefundRequest( + BigDecimal refundAmount, // type check 필요 + String refundReason +) { +} diff --git a/src/main/java/poomasi/domain/order/_aftersales/entity/_abstract/AbstractAfterSales.java b/src/main/java/poomasi/domain/order/_aftersales/entity/_abstract/AbstractAfterSales.java new file mode 100644 index 00000000..cbbe0afc --- /dev/null +++ b/src/main/java/poomasi/domain/order/_aftersales/entity/_abstract/AbstractAfterSales.java @@ -0,0 +1,77 @@ +package poomasi.domain.order._aftersales.entity._abstract; + +import jakarta.persistence.*; +import jdk.jfr.Timestamp; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; + +import java.time.LocalDateTime; + +@MappedSuperclass +public abstract class AbstractAfterSales { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "created_at") + @CreationTimestamp + private LocalDateTime createdAt = LocalDateTime.now(); + + @Column(name = "updated_at") + @UpdateTimestamp + private LocalDateTime updateAt = LocalDateTime.now(); + + @Column(name = "deleted_at") + @Timestamp + private LocalDateTime deletedAt; + +} + +/* +* package poomasi.domain.order._aftersales.entity; + +import jakarta.persistence.*; +import jdk.jfr.Description; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; +import poomasi.domain.order._payment.entity.Payment; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Entity +@Table(name="refund_history") +public class Refund { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @CreationTimestamp + @Column(name = "created_at") + private LocalDateTime createdAt = LocalDateTime.now(); + + @Column(name = "updated_at") + @UpdateTimestamp + private LocalDateTime updatedAt = LocalDateTime.now(); + + @Description("삭제 시간") + private LocalDateTime deletedAt; + + @ManyToOne(fetch = FetchType.LAZY) + private Payment payment; + + + private BigDecimal refundAmount; + + @OneToOne(fetch = FetchType.LAZY) + private OrderedProduct orderProductDetails; + +private String refundReason; + +} + + * +* */ + diff --git a/src/main/java/poomasi/domain/order/_aftersales/entity/_farm/FarmAfterSales.java b/src/main/java/poomasi/domain/order/_aftersales/entity/_farm/FarmAfterSales.java new file mode 100644 index 00000000..055a8ca3 --- /dev/null +++ b/src/main/java/poomasi/domain/order/_aftersales/entity/_farm/FarmAfterSales.java @@ -0,0 +1,6 @@ +package poomasi.domain.order._aftersales.entity._farm; + +import poomasi.domain.order._aftersales.entity._abstract.AbstractAfterSales; + +public class FarmAfterSales extends AbstractAfterSales { +} diff --git a/src/main/java/poomasi/domain/order/_aftersales/entity/_product/ProductAfterSales.java b/src/main/java/poomasi/domain/order/_aftersales/entity/_product/ProductAfterSales.java new file mode 100644 index 00000000..f2345caa --- /dev/null +++ b/src/main/java/poomasi/domain/order/_aftersales/entity/_product/ProductAfterSales.java @@ -0,0 +1,21 @@ +package poomasi.domain.order._aftersales.entity._product; + +import jakarta.persistence.*; +import poomasi.domain.order._aftersales.entity._abstract.AbstractAfterSales; +import poomasi.domain.order.entity._product.ProductOrder; + +import java.util.List; + +@Entity +@Table(name="product_after_sales") +public class ProductAfterSales extends AbstractAfterSales { + + @OneToOne + @JoinColumn(name = "product_order_id") + private ProductOrder productOrder; + + @OneToMany + private List productAfterSalesDetail; + +} + diff --git a/src/main/java/poomasi/domain/order/_aftersales/entity/_product/ProductAfterSalesDetail.java b/src/main/java/poomasi/domain/order/_aftersales/entity/_product/ProductAfterSalesDetail.java new file mode 100644 index 00000000..b37f4fbb --- /dev/null +++ b/src/main/java/poomasi/domain/order/_aftersales/entity/_product/ProductAfterSalesDetail.java @@ -0,0 +1,42 @@ + package poomasi.domain.order._aftersales.entity._product; + + import jakarta.persistence.*; + import jdk.jfr.Description; + import poomasi.domain.order.entity._product.OrderedProduct; + + import java.math.BigDecimal; + + @Description("상품 판매 후 교환/환불/추소 history") + @Entity + @Table(name="product_after_sales_detail") + public class ProductAfterSalesDetail { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne + private ProductAfterSales productAfterSales; + + @OneToOne + private OrderedProduct orderedProduct; + + + @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) + @JoinColumn(name = "refund_exchange_detail_id", nullable = true) // 외래 키 설정 + private RefundExchangeDetail refundExchangeDetail; + + @Description("ordered products의 환불/교환/취소 금액") + private BigDecimal amount; + + @Description("환불/교환/취소 사유") + private String reason; + + @Description("환불 받을 계좌번호") + private String refundAccount; + + @Enumerated(EnumType.STRING) + private ProductAfterSalesType productAfterSalesType; + + + } diff --git a/src/main/java/poomasi/domain/order/_aftersales/entity/_product/ProductAfterSalesType.java b/src/main/java/poomasi/domain/order/_aftersales/entity/_product/ProductAfterSalesType.java new file mode 100644 index 00000000..cfe7f4a8 --- /dev/null +++ b/src/main/java/poomasi/domain/order/_aftersales/entity/_product/ProductAfterSalesType.java @@ -0,0 +1,7 @@ +package poomasi.domain.order._aftersales.entity._product; + +public enum ProductAfterSalesType { + EXCHANGE, + CANCEL, + REFUND +} diff --git a/src/main/java/poomasi/domain/order/_aftersales/entity/_product/RefundExchangeDetail.java b/src/main/java/poomasi/domain/order/_aftersales/entity/_product/RefundExchangeDetail.java new file mode 100644 index 00000000..6e81e378 --- /dev/null +++ b/src/main/java/poomasi/domain/order/_aftersales/entity/_product/RefundExchangeDetail.java @@ -0,0 +1,29 @@ +package poomasi.domain.order._aftersales.entity._product; + + +import jakarta.persistence.*; +import jdk.jfr.Description; + +@Entity +@Table(name= "refund_exchange_detail") +public class RefundExchangeDetail { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @OneToOne(mappedBy = "refundExchangeDetail") // 주인이 아닌 쪽에 mappedBy 설정 + private ProductAfterSalesDetail productAfterSalesDetail; + + @Description("반품 회수지. 기본 값은 보낸 주소") + private String pickupLocation; + + @Description("반송지. 기본 값은 받은 주소") + private String returnAddress; + + @Description("반품/교환 시 운송장 번호") + private String invoiceNumber; + + @Description("반품/교환 시 요청 사항") + private String request; +} diff --git a/src/main/java/poomasi/domain/order/_aftersales/repository/ProductAfterSalesRepository.java b/src/main/java/poomasi/domain/order/_aftersales/repository/ProductAfterSalesRepository.java new file mode 100644 index 00000000..9d274f26 --- /dev/null +++ b/src/main/java/poomasi/domain/order/_aftersales/repository/ProductAfterSalesRepository.java @@ -0,0 +1,7 @@ +package poomasi.domain.order._aftersales.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import poomasi.domain.order._aftersales.entity._product.ProductAfterSales; + +public interface ProductAfterSalesRepository extends JpaRepository { +} diff --git a/src/main/java/poomasi/domain/order/_aftersales/service/RefundService.java b/src/main/java/poomasi/domain/order/_aftersales/service/RefundService.java new file mode 100644 index 00000000..4d47f74e --- /dev/null +++ b/src/main/java/poomasi/domain/order/_aftersales/service/RefundService.java @@ -0,0 +1,22 @@ +package poomasi.domain.order._aftersales.service; + + +import com.siot.IamportRestClient.IamportClient; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import poomasi.domain.order._aftersales.repository.ProductAfterSalesRepository; + +@Service +@RequiredArgsConstructor +public class RefundService { + + private final ProductAfterSalesRepository productAfterSalesRepository; + private final IamportClient iamportClient; + + + /* public void refundFull(){ + iamportClient. + }*/ + + +} diff --git a/src/main/java/poomasi/domain/order/_payment/controller/PaymentController.java b/src/main/java/poomasi/domain/order/_payment/controller/PaymentController.java index 84acdd44..21324d48 100644 --- a/src/main/java/poomasi/domain/order/_payment/controller/PaymentController.java +++ b/src/main/java/poomasi/domain/order/_payment/controller/PaymentController.java @@ -8,7 +8,6 @@ import org.springframework.web.bind.annotation.*; import poomasi.domain.order._payment.dto.request.PaymentPreRegisterRequest; import poomasi.domain.order._payment.dto.request.PaymentWebHookRequest; -import poomasi.domain.order._payment.dto.response.PaymentResponse; import poomasi.domain.order._payment.service.PaymentService; import java.io.IOException; @@ -23,21 +22,36 @@ public class PaymentController { @Description("사전 결제 api") @Secured({"ROLE_CUSTOMER", "ROLE_FARMER"}) @PostMapping("/pre-payment") - public void postPrepare(PaymentPreRegisterRequest paymentPreRegisterRequest) throws IamportResponseException, IOException { - paymentService.portonePrePaymentRegister(paymentPreRegisterRequest); + public ResponseEntity postPrepare(PaymentPreRegisterRequest paymentPreRegisterRequest) throws IamportResponseException, IOException { + return ResponseEntity.ok( + paymentService.portonePrePaymentRegister(paymentPreRegisterRequest) + ); } @Description("사후 결제(검증 api)") @PostMapping("/validate") - public void validatePayment(PaymentWebHookRequest paymentWebHookRequest) throws IamportResponseException, IOException { - paymentService.portoneVerifyPostPayment(paymentWebHookRequest); + public void validatePayment(@RequestBody PaymentWebHookRequest paymentWebHookRequest) throws IamportResponseException, IOException { + paymentService.handlePortOneProductWebhookEvent(paymentWebHookRequest); + } + + + @Description("포트원 웹훅 수신 api") + @PostMapping("/portone-webhook") + public void handleIamportWebhook(@RequestBody PaymentWebHookRequest paymentWebHookRequest) throws IamportResponseException, IOException { + paymentService.handlePortOneProductWebhookEvent(paymentWebHookRequest); + } + + + @Description("결제 바로 직전 포트원에서 보내는 confirm 요청" + " 결제를 진행하려면 HTTP Status 200 응답, 그렇지 않으면 500 응답 보내기" ) + @PostMapping("/confirm-stock/") + public ResponseEntity confirmProductStock(@RequestParam String merchantUid, @RequestParam String impUid) throws IamportResponseException, IOException { + paymentService.confirmProductStock(merchantUid, impUid); + return ResponseEntity.ok().build(); } - /* - *@Description("포트원 webhook + 동기화") - * */ + /* @GetMapping("/") @Secured("ROLE_CUSTOMER") @@ -55,5 +69,12 @@ public ResponseEntity getPaymentByOrderId(@RequestParam Long orderId){ return ResponseEntity.ok(paymentResponse); } + */ + } + +/**TODO : filter 만들어서 webhook URL에 대해 IP 검증해야 함 + *@Description("포트원 webhook + 동기화") + @PostMapping("/portone-webhook") + * */ \ No newline at end of file diff --git a/src/main/java/poomasi/domain/order/_payment/dto/request/PaymentPreRegisterRequest.java b/src/main/java/poomasi/domain/order/_payment/dto/request/PaymentPreRegisterRequest.java index c2df4c06..24fb5aae 100644 --- a/src/main/java/poomasi/domain/order/_payment/dto/request/PaymentPreRegisterRequest.java +++ b/src/main/java/poomasi/domain/order/_payment/dto/request/PaymentPreRegisterRequest.java @@ -3,5 +3,4 @@ import java.math.BigDecimal; public record PaymentPreRegisterRequest(String merchantUid, BigDecimal amount) { - } diff --git a/src/main/java/poomasi/domain/order/_payment/dto/request/PaymentValidateRequest.java b/src/main/java/poomasi/domain/order/_payment/dto/request/PaymentValidateRequest.java new file mode 100644 index 00000000..51639b27 --- /dev/null +++ b/src/main/java/poomasi/domain/order/_payment/dto/request/PaymentValidateRequest.java @@ -0,0 +1,4 @@ +package poomasi.domain.order._payment.dto.request; + +public record PaymentValidateRequest(String merchantUid, String amount) { +} diff --git a/src/main/java/poomasi/domain/order/_payment/dto/request/PaymentWebHookRequest.java b/src/main/java/poomasi/domain/order/_payment/dto/request/PaymentWebHookRequest.java index a735aa9b..b9f68f75 100644 --- a/src/main/java/poomasi/domain/order/_payment/dto/request/PaymentWebHookRequest.java +++ b/src/main/java/poomasi/domain/order/_payment/dto/request/PaymentWebHookRequest.java @@ -1,5 +1,8 @@ package poomasi.domain.order._payment.dto.request; -public record PaymentWebHookRequest(String imp_uid, - String merchant_uid) { +import com.fasterxml.jackson.annotation.JsonProperty; + +public record PaymentWebHookRequest(@JsonProperty("imp_uid") String impUid, + @JsonProperty("merchant_uid") String merchantUid, + String status) { } diff --git a/src/main/java/poomasi/domain/order/_payment/dto/response/PaymentResponse.java b/src/main/java/poomasi/domain/order/_payment/dto/response/PaymentResponse.java index 9f6d0fa6..14f8ff20 100644 --- a/src/main/java/poomasi/domain/order/_payment/dto/response/PaymentResponse.java +++ b/src/main/java/poomasi/domain/order/_payment/dto/response/PaymentResponse.java @@ -6,7 +6,6 @@ import java.math.BigDecimal; public record PaymentResponse(Long paymentId, - String merchantUid, BigDecimal totalPrice, BigDecimal discountPrice, BigDecimal finalPrice, @@ -15,7 +14,6 @@ public record PaymentResponse(Long paymentId, public static PaymentResponse fromEntity(Payment payment){ return new PaymentResponse( payment.getId(), - payment.getMerchantUid(), payment.getTotalPrice(), payment.getDiscountPrice(), payment.getFinalPrice(), diff --git a/src/main/java/poomasi/domain/order/_payment/entity/Payment.java b/src/main/java/poomasi/domain/order/_payment/entity/Payment.java index 321faa88..ddc3048d 100644 --- a/src/main/java/poomasi/domain/order/_payment/entity/Payment.java +++ b/src/main/java/poomasi/domain/order/_payment/entity/Payment.java @@ -1,12 +1,13 @@ package poomasi.domain.order._payment.entity; - import jakarta.persistence.*; import jdk.jfr.Description; import lombok.Getter; -import poomasi.domain.order.entity.Order; +import poomasi.domain.order.entity._farm.FarmOrder; +import poomasi.domain.order.entity._product.ProductOrder; import java.math.BigDecimal; +import java.util.List; @Entity @Getter @@ -16,12 +17,15 @@ public class Payment { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @Description("상품 총 가격") + @Description("포트원 결제 금액") private BigDecimal totalPrice; @Description("할인 가격") private BigDecimal discountPrice; - + + @Description("사용 포인트") + private BigDecimal usedPoint; + @Description("최종 가격") private BigDecimal finalPrice; @@ -29,11 +33,10 @@ public class Payment { @Enumerated(EnumType.STRING) private PaymentMethod paymentMethod; - @OneToOne - private Order order; + @OneToOne(mappedBy = "payment") + private ProductOrder productOrder; - @Description("포트원에서 결제 식별을 위한 merchant_uid") - @Column(name = "merchant_uid" , updatable = false) - private String merchantUid; +/* @OneToOne(mappedBy = "payment") + private FarmOrder farmOrder;*/ } diff --git a/src/main/java/poomasi/domain/order/_payment/entity/PaymentState.java b/src/main/java/poomasi/domain/order/_payment/entity/PaymentState.java index 77d04d38..d160f719 100644 --- a/src/main/java/poomasi/domain/order/_payment/entity/PaymentState.java +++ b/src/main/java/poomasi/domain/order/_payment/entity/PaymentState.java @@ -1,5 +1,9 @@ package poomasi.domain.order._payment.entity; + +import jdk.jfr.Description; + +@Description("임시로 남겨둠 .. . .") public enum PaymentState { PENDING, // 결제 대기 중 COMPLETED, // 결제 완료 diff --git a/src/main/java/poomasi/domain/order/_payment/service/PaymentService.java b/src/main/java/poomasi/domain/order/_payment/service/PaymentService.java index e5d33028..a65ec9dc 100644 --- a/src/main/java/poomasi/domain/order/_payment/service/PaymentService.java +++ b/src/main/java/poomasi/domain/order/_payment/service/PaymentService.java @@ -4,17 +4,15 @@ import com.siot.IamportRestClient.exception.IamportResponseException; import com.siot.IamportRestClient.request.CancelData; import com.siot.IamportRestClient.request.PrepareData; -import com.siot.IamportRestClient.response.AccessToken; import com.siot.IamportRestClient.response.IamportResponse; -import com.siot.IamportRestClient.response.Prepare; import jdk.jfr.Description; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.ResponseEntity; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Transactional; import poomasi.domain.auth.security.userdetail.UserDetailsImpl; import poomasi.domain.member.entity.Member; @@ -24,15 +22,22 @@ import poomasi.domain.order._payment.dto.response.PaymentResponse; import poomasi.domain.order._payment.entity.Payment; import poomasi.domain.order._payment.repository.PaymentRepository; -import poomasi.domain.order.entity.Order; -import poomasi.domain.order.repository.OrderRepository; +import poomasi.domain.order.entity._product.OrderedProduct; +import poomasi.domain.order.entity._product.ProductOrder; +import poomasi.domain.order.repository.ProductOrderRepository; import poomasi.domain.product._cart.service.CartService; -import poomasi.global.error.BusinessError; +import poomasi.domain.product.entity.Product; import poomasi.global.error.BusinessException; +import poomasi.global.error.PaymentConfirmError; +import poomasi.global.error.PaymentConfirmException; import java.io.IOException; import java.math.BigDecimal; import java.util.List; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import static poomasi.domain.order.entity.OrderStatus.AWAITING_SELLER_CONFIRMATION; import static poomasi.domain.order.entity.OrderStatus.PENDING; @@ -46,18 +51,15 @@ public class PaymentService { @Autowired private final PaymentRepository paymentRepository; private final IamportClient iamportClient; - private final OrderRepository orderRepository; + private final ProductOrderRepository productOrderRepository; private final CartService cartService; - @Description("포트원 api 호출을 위한 accessToken 발급 메서드") - private String getPortOneAccessToken() throws IOException, IamportResponseException { - IamportResponse authResponse = iamportClient.getAuth(); - String accessToken = authResponse.getResponse().getToken(); - return accessToken; - } - - @Description("사전 결제 등록") + private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); + private final AtomicBoolean isWebhookReceived = new AtomicBoolean(false); // 웹훅 수신 여부 체크 + + @Description("사전 결제 등록. 프론트엔드에게 서버 merchant uid를 return 해야 함") public PaymentPreRegisterResponse portonePrePaymentRegister(PaymentPreRegisterRequest paymentPreRegisterRequest) throws IOException, IamportResponseException { + PrepareData prepareData = new PrepareData(paymentPreRegisterRequest.merchantUid(), paymentPreRegisterRequest.amount() ); @@ -67,7 +69,123 @@ public PaymentPreRegisterResponse portonePrePaymentRegister(PaymentPreRegisterRe ); } - @Transactional + + @Transactional(isolation = Isolation.SERIALIZABLE) + @Description("포트원 결제 직전 바로 받는 confirm 요청. 40초 대기") + public void confirmProductStock(String impUid, String merchantUid) throws IOException, IamportResponseException { + + ProductOrder productOrder = productOrderRepository.findByMerchantUidAndImpUid(merchantUid, impUid) + .orElseThrow(() -> new BusinessException(PAYMENT_NOT_FOUND)); + + List orderedProductList = productOrder.getOrderedProducts(); + //수량 검증 + for(OrderedProduct orderedProduct : orderedProductList) { + Product product = orderedProduct.getProduct(); + Integer remainQuantity = product.getStock(); + Integer orderQuantity = orderedProduct.getCount(); + + //주문 재고가 남은 재고보다 많다면 500 + reason 보내야 함 + if(orderQuantity > remainQuantity){ + throw new PaymentConfirmException(PaymentConfirmError.PAYMENT_PROUCT_CONFIRM_EXCEPTION); + } + } + BigDecimal amountToBePaid = productOrder.getTotalAmount(); + //재고 검증 완료 -> 200 OK 보내야 함 + 웹훅 수신 여부에 따라 분기 + scheduler.schedule(() -> { + try { + if (!isWebhookReceived.get()) { //수신 못 받으면 + if(sendAndValidateAmount(impUid, amountToBePaid)){ //impUid를 가지고 포트원 서버에 요청을 한 후, db와 결제 금액 비교한다. + productOrder.setOrderStatus(AWAITING_SELLER_CONFIRMATION); + decreaseStock(productOrder); + }else{ + //실제 결제 된 금액과 결제 되어야 할 금액이 다르다면 -> 결제 취소 api를 호출해야 한다. + cancelPaymentByImpUid(impUid); + } + } + } catch (IOException | IamportResponseException e) { + e.printStackTrace(); + } + }, 40, TimeUnit.SECONDS); + + } + + @Description("단건 조회 후, 결제 되어야 할 금액과 결제 된 금액이 같은지 확인하는 메서드") + public boolean sendAndValidateAmount(String impUid, BigDecimal amountToBePaid) throws IOException, IamportResponseException{ + BigDecimal amount = getPaymentAmount(impUid); + if(amountToBePaid.compareTo(amount)==0){ + return true; + } + return false; + } + + + @Description("포트원에서 결제 금액 조회하는 메서드") + public BigDecimal getPaymentAmount(String impUid) throws IOException, IamportResponseException{ + IamportResponse iamportResponse = getSingleTransaction(impUid); + return iamportResponse.getResponse().getAmount(); + } + + @Description("웹훅 처리 service -> 결제 정상적으로 성공됨을 보장") + @Transactional(isolation = Isolation.SERIALIZABLE) + public void handlePortOneProductWebhookEvent(PaymentWebHookRequest paymentWebHookRequest) throws IOException, IamportResponseException { + + isWebhookReceived.set(true); //웹훅 수신 플래그 설정하기 + + String impUid = paymentWebHookRequest.impUid(); + ProductOrder productOrder = productOrderRepository.findByImpUid(impUid) + .orElseThrow(() -> new BusinessException(PAYMENT_NOT_FOUND)); + BigDecimal amountToBePaid = productOrder.getTotalAmount(); + + //결제 되어야 할 금액과 결제 된 금액이 같다면 + if(sendAndValidateAmount(impUid, amountToBePaid)){ + productOrder.setOrderStatus(AWAITING_SELLER_CONFIRMATION); + decreaseStock(productOrder); + }else{ + cancelPaymentByImpUid(impUid); //실제 결제 된 금액과 결제 되어야 할 금액이 다르다면 -> 결제 취소 api를 호출해야 한다. + //throw new BusinessException + } + + } + + @Description("재고 차감 메서드") + @Transactional(isolation = Isolation.SERIALIZABLE) + public void decreaseStock(ProductOrder productOrder){ + List orderedProductList = productOrder.getOrderedProducts(); + for (OrderedProduct orderedProduct : orderedProductList){ + Product product = orderedProduct.getProduct(); + Integer remainQuantity = product.getStock(); //남은 수량 + Integer subtractQuantity = orderedProduct.getCount();//빼야 할 수량 + if(subtractQuantity > remainQuantity){ + throw new BusinessException(STOCK_QUANTITY_EXCEEDED); + } + product.subtractStock(subtractQuantity); + } + } + + /* + com.siot.IamportRestClient.response.Payment payment = iamportResponse.getResponse(); + int code = iamportResponse.getCode(); + String message = iamportResponse.getMessage(); + String status = payment.getStatus(); + BigDecimal amount = payment.getAmount(); + */ + @Description("단건 결제 조회 API") + public IamportResponse getSingleTransaction(String impUid) throws IOException, IamportResponseException { + IamportResponse iamportResponse = iamportClient.paymentByImpUid(impUid); + return iamportResponse; + } + + @Description("서버에서 마지막 검증") + public boolean verifyPostPayment(String impUid) throws IOException, IamportResponseException { + ProductOrder productOrder = productOrderRepository.findByImpUid(impUid) + .orElseThrow(() -> new BusinessException(PAYMENT_NOT_FOUND)); + if(productOrder.getOrderStatus() == PENDING){ + throw new BusinessException(PAYMENT_BAD_REQUEST); + } + return true; + } + + /*@Transactional @Description("프론트에서 받아온 결과를 validate하는 메서드") public void portoneVerifyPostPayment(PaymentWebHookRequest paymentWebHookRequest) throws IOException, IamportResponseException { String impUid = paymentWebHookRequest.imp_uid(); @@ -76,7 +194,7 @@ public void portoneVerifyPostPayment(PaymentWebHookRequest paymentWebHookRequest BigDecimal amount = iamportResponse.getResponse() .getAmount(); - Order order = orderRepository.findByMerchantUid(merchantUid) + ProductOrder order = productOrderRepository.findByMerchantUid(merchantUid) .orElseThrow(() -> new BusinessException(ORDER_NOT_FOUND)); if(order.getOrderStatus()!=PENDING){ //이미 처리한 주문이라면 @@ -89,7 +207,10 @@ public void portoneVerifyPostPayment(PaymentWebHookRequest paymentWebHookRequest } order.setOrderStatus(AWAITING_SELLER_CONFIRMATION); // 상태 변경 cartService.removeSelected(); //장바구니 삭제 - } + }*/ + + //private boolean compareCartAndPaymentAmount(Cart cart, BigDecimal ) + private boolean validatePaymentConsistency(BigDecimal prepaymentAmount, BigDecimal postPaymentAmount){ if (prepaymentAmount.compareTo(postPaymentAmount) != 0) { @@ -98,26 +219,35 @@ private boolean validatePaymentConsistency(BigDecimal prepaymentAmount, BigDecim return true; } - @Description("payment 상세 내역 조회를 위한 단건 api 호출") - public void getPaymentDetails(String merchantUid, Long orderId) throws IOException, IamportResponseException { + public void cancelPaymentByImpUid(String impUid) throws IOException, IamportResponseException { + CancelData cancelDate = new CancelData(impUid, false); + iamportClient.cancelPaymentByImpUid(cancelDate); } - @Description("결제 취소 api") - public void cancelPayment(IamportResponse response) throws IOException, IamportResponseException{ + /*@Transactional + @Description("결제 전액 취소/환불 api") + public void cancelPayment(Payment payment, IamportResponse response) throws IOException, IamportResponseException{ + //TODO : 결제내역 단건 조회 통해서 이미 완료가 된 결제인지 확인 //true면 Uid, false면 merchantUid로 판단 CancelData cancelData = new CancelData(response.getResponse().getMerchantUid(), false); iamportClient.cancelPaymentByImpUid(cancelData); - } - @Description("결제 환불 api") + }*/ + + /* @Description("결제 환불 api") public void processRefund() throws IOException, IamportResponseException{ } +*/ - + @Transactional @Description("결제 부분 환불 api 호출") - public void partialRefund() throws IOException, IamportResponseException { + public void partialRefund(BigDecimal checkSum, IamportResponse response, BigDecimal amount) throws IOException, IamportResponseException { + //BigDecimal checkSum = payment.getChecksum(); + CancelData cancelData = new CancelData(response.getResponse().getMerchantUid(), false, amount); + cancelData.setChecksum(checkSum); + iamportClient.cancelPaymentByImpUid(cancelData); } diff --git a/src/main/java/poomasi/domain/order/_refund/entity/Refund.java b/src/main/java/poomasi/domain/order/_refund/entity/Refund.java deleted file mode 100644 index 8b51294d..00000000 --- a/src/main/java/poomasi/domain/order/_refund/entity/Refund.java +++ /dev/null @@ -1,20 +0,0 @@ -package poomasi.domain.order._refund.entity; - - -import jakarta.persistence.*; -import poomasi.domain.order.entity.OrderProductDetails; - -@Entity -@Table(name="refund") -public class Refund { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - -/* @OneToOne(fetch = FetchType.LAZY) - private OrderProductDetails orderProductDetails;*/ - - private String refundReason; - -} diff --git a/src/main/java/poomasi/domain/order/_refund/entity/RefundStatus.java b/src/main/java/poomasi/domain/order/_refund/entity/RefundStatus.java deleted file mode 100644 index 69a08f7c..00000000 --- a/src/main/java/poomasi/domain/order/_refund/entity/RefundStatus.java +++ /dev/null @@ -1,29 +0,0 @@ -package poomasi.domain.order._refund.entity; - -public enum RefundStatus { - REQUESTED("환불 요청됨"), - PROCESSING("환불 처리 중"), - COMPLETED("환불 완료됨"), - REJECTED("환불 거절됨"); - - private final String description; - - RefundStatus(String description) { - this.description = description; - } - - public String getDescription() { - return description; - } -} - -/* -* REQUESTED, // 반품 요청됨 - APPROVED, // 반품 승인됨 - REJECTED, // 반품 거부됨 - RETURNED, // 상품이 반품됨 - REFUNDED, // 환불 완료됨 - CANCELLED, // 반품 요청이 취소됨 - IN_TRANSIT // 반품이 배송 중 - ; -* */ diff --git a/src/main/java/poomasi/domain/order/controller/OrderController.java b/src/main/java/poomasi/domain/order/controller/OrderController.java index 8a8c7a81..a6e17aa1 100644 --- a/src/main/java/poomasi/domain/order/controller/OrderController.java +++ b/src/main/java/poomasi/domain/order/controller/OrderController.java @@ -12,6 +12,7 @@ import poomasi.domain.auth.security.userdetail.UserDetailsImpl; import poomasi.domain.order._payment.dto.request.PaymentPreRegisterRequest; import poomasi.domain.order._payment.service.PaymentService; +import poomasi.domain.order.dto.request.OrderRegisterRequest; import poomasi.domain.order.service.OrderService; import java.io.IOException; @@ -27,15 +28,26 @@ public class OrderController { private final PaymentService paymentService; @Secured({"ROLE_CUSTOMER", "ROLE_FARMER"}) - @PostMapping("/pre-order") - public ResponseEntity createPreOrder(@AuthenticationPrincipal UserDetailsImpl user) throws IOException, IamportResponseException { - PaymentPreRegisterRequest paymentPreRegisterRequest = orderService.preOrderRegister(); + @PostMapping("/product/pre-order") + @Description("product 사전 결제") + public ResponseEntity createProductPreOrder(@RequestBody OrderRegisterRequest orderRegisterRequest) throws IOException, IamportResponseException { + PaymentPreRegisterRequest paymentPreRegisterRequest = orderService.productPreOrderRegister(orderRegisterRequest); return ResponseEntity.ok( paymentService.portonePrePaymentRegister(paymentPreRegisterRequest) ); } - - @Description("멤버의 결제 완료가 된 단건 주문 조회") + + @Secured({"ROLE_CUSTOMER", "ROLE_FARMER"}) + @PostMapping("/farm/pre-order") + @Description("farm 사전 결제") + public ResponseEntity createFarmPreOrder() throws IOException, IamportResponseException { + PaymentPreRegisterRequest paymentPreRegisterRequest = orderService.farmPreOrderRegister(); + return ResponseEntity.ok( + paymentService.portonePrePaymentRegister(paymentPreRegisterRequest) + ); + } + + @Description("멤버의 결제 완료가 된 단건 주문 조회. 특정 건만 조회") @GetMapping("/{orderId}") public ResponseEntity getAllOrdersByMember(@PathVariable Long orderId) { return ResponseEntity.ok( @@ -43,7 +55,7 @@ public ResponseEntity getAllOrdersByMember(@PathVariable Long orderId) { ); } - @Description("멤버의 결제 완료가 된 전체 주문 목록 조회") + @Description("멤버의 결제 완료가 된 전체 주문 목록 조회. 전체 주문 목록 조회") @GetMapping("/") public ResponseEntity getOrdersByMember() { return ResponseEntity.ok( diff --git a/src/main/java/poomasi/domain/order/dto/request/OrderRegisterRequest.java b/src/main/java/poomasi/domain/order/dto/request/OrderRegisterRequest.java new file mode 100644 index 00000000..b29c8c96 --- /dev/null +++ b/src/main/java/poomasi/domain/order/dto/request/OrderRegisterRequest.java @@ -0,0 +1,6 @@ +package poomasi.domain.order.dto.request; + +public record OrderRegisterRequest(String address, + String addressDetails, + String deliveryRequest) { +} diff --git a/src/main/java/poomasi/domain/order/dto/response/OrderDetailsResponse.java b/src/main/java/poomasi/domain/order/dto/response/OrderDetailsResponse.java index c93a0358..e3002a7c 100644 --- a/src/main/java/poomasi/domain/order/dto/response/OrderDetailsResponse.java +++ b/src/main/java/poomasi/domain/order/dto/response/OrderDetailsResponse.java @@ -1,17 +1,17 @@ package poomasi.domain.order.dto.response; -import poomasi.domain.order.entity.OrderDetails; +import poomasi.domain.order.entity._product.ProductOrderDetails; public record OrderDetailsResponse( String address, String addressDetails, String deliveryRequest ) { - public static OrderDetailsResponse fromEntity(OrderDetails orderDetails) { + public static OrderDetailsResponse fromEntity(ProductOrderDetails productOrderDetails) { return new OrderDetailsResponse( - orderDetails.getAddress(), - orderDetails.getAddressDetail(), - orderDetails.getDeliveryRequest() + productOrderDetails.getAddress(), + productOrderDetails.getAddressDetail(), + productOrderDetails.getDeliveryRequest() ); } } diff --git a/src/main/java/poomasi/domain/order/dto/response/OrderProductDetailsResponse.java b/src/main/java/poomasi/domain/order/dto/response/OrderProductDetailsResponse.java index 9e2ae945..1726d646 100644 --- a/src/main/java/poomasi/domain/order/dto/response/OrderProductDetailsResponse.java +++ b/src/main/java/poomasi/domain/order/dto/response/OrderProductDetailsResponse.java @@ -1,7 +1,6 @@ package poomasi.domain.order.dto.response; -import poomasi.domain.order.entity.OrderProductDetails; -import poomasi.domain.product.dto.ProductResponse; +import poomasi.domain.order.entity._product.OrderedProduct; import java.math.BigDecimal; @@ -14,15 +13,15 @@ public record OrderProductDetailsResponse( BigDecimal price, //총 결제 금액 String invoiceNumber ) { - public static OrderProductDetailsResponse fromEntity(OrderProductDetails orderProductDetails) { + public static OrderProductDetailsResponse fromEntity(OrderedProduct orderedProduct) { return new OrderProductDetailsResponse( - orderProductDetails.getOrder().getId(), - orderProductDetails.getId(), - orderProductDetails.getProduct().getId(), - orderProductDetails.getProductName(), - orderProductDetails.getCount(), - orderProductDetails.getPrice(), - orderProductDetails.getInvoiceNumber() + orderedProduct.getOrderId(), + orderedProduct.getId(), + orderedProduct.getProduct().getId(), + orderedProduct.getProductName(), + orderedProduct.getCount(), + orderedProduct.getPrice(), + orderedProduct.getInvoiceNumber() ); } diff --git a/src/main/java/poomasi/domain/order/dto/response/OrderResponse.java b/src/main/java/poomasi/domain/order/dto/response/OrderResponse.java index 77f8a793..e5621032 100644 --- a/src/main/java/poomasi/domain/order/dto/response/OrderResponse.java +++ b/src/main/java/poomasi/domain/order/dto/response/OrderResponse.java @@ -1,8 +1,6 @@ package poomasi.domain.order.dto.response; -import poomasi.domain.order._payment.dto.response.PaymentResponse; -import poomasi.domain.order.entity.Order; -import poomasi.domain.order.entity.OrderStatus; +import poomasi.domain.order.entity._product.ProductOrder; import java.time.LocalDateTime; import java.util.List; @@ -12,12 +10,12 @@ public record OrderResponse(Long orderId, String merchantUid, LocalDateTime createdAt, List orderProductDetailsResponseList) { - public static OrderResponse fromEntity(Order order) { + public static OrderResponse fromEntity(ProductOrder productOrder) { return new OrderResponse( - order.getId(), - order.getMerchantUid(), - order.getCreatedAt(), - order.getOrderProductDetails() + productOrder.getId(), + productOrder.getMerchantUid(), + productOrder.getCreatedAt(), + productOrder.getOrderedProducts() .stream() .map(OrderProductDetailsResponse::fromEntity) .collect(Collectors.toList()) diff --git a/src/main/java/poomasi/domain/order/entity/AbstractOrder.java b/src/main/java/poomasi/domain/order/entity/AbstractOrder.java deleted file mode 100644 index 82a7c4ff..00000000 --- a/src/main/java/poomasi/domain/order/entity/AbstractOrder.java +++ /dev/null @@ -1,33 +0,0 @@ -package poomasi.domain.order.entity; - -import jakarta.persistence.*; -import jdk.jfr.Description; -import jdk.jfr.Timestamp; -import lombok.Getter; -import org.hibernate.annotations.UpdateTimestamp; -import poomasi.domain.member.entity.Member; - -import java.time.LocalDateTime; -import java.util.Date; - -@MappedSuperclass -@Getter -public abstract class AbstractOrder { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @JoinColumn(name = "member_id") - @ManyToOne(fetch = FetchType.LAZY) - private Member member; - - @Column(name = "merchant_uid") - @Description("상품당 결제 id(아임포트 id)") - private String merchantUid = "p" + new Date().getTime(); - - @Column(name = "created_at") - @Timestamp - private LocalDateTime createdAt = LocalDateTime.now(); - -} diff --git a/src/main/java/poomasi/domain/order/entity/Order.java b/src/main/java/poomasi/domain/order/entity/Order.java deleted file mode 100644 index a830bed2..00000000 --- a/src/main/java/poomasi/domain/order/entity/Order.java +++ /dev/null @@ -1,49 +0,0 @@ -package poomasi.domain.order.entity; - - -import jakarta.persistence.*; -import lombok.Getter; -import lombok.NoArgsConstructor; - -import java.math.BigDecimal; -import java.util.List; - -import static poomasi.domain.order.entity.OrderStatus.PENDING; - -@Entity -@Table(name = "orders") -@Getter -@NoArgsConstructor -public class Order extends AbstractOrder{ - - @Column(name = "order_product_details_id") - @OneToMany(mappedBy = "order", cascade = CascadeType.ALL) - private List orderProductDetails; - - @OneToOne - @JoinColumn(name = "order_details_id") // 여기서 JoinColumn 사용 - private OrderDetails orderDetails; - - @Column(name = "total_amount") - private BigDecimal totalAmount; - - @Enumerated(EnumType.STRING) - private OrderStatus orderStatus = OrderStatus.PENDING; - - public Order(OrderDetails orderDetails) { - this.orderDetails = orderDetails; - } - - public void addOrderDetail(OrderProductDetails orderProductDetails) { - this.orderProductDetails.add(orderProductDetails); - } - public void setOrderStatus(OrderStatus orderStatus) { - this.orderStatus = orderStatus; - } - - public void setTotalAmount(BigDecimal totalAmount) { - this.totalAmount = totalAmount; - } - - -} diff --git a/src/main/java/poomasi/domain/order/entity/OrderProductDetails.java b/src/main/java/poomasi/domain/order/entity/OrderProductDetails.java deleted file mode 100644 index 5be2e668..00000000 --- a/src/main/java/poomasi/domain/order/entity/OrderProductDetails.java +++ /dev/null @@ -1,71 +0,0 @@ -package poomasi.domain.order.entity; - - -import jakarta.persistence.*; -import jdk.jfr.Description; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -import poomasi.domain.order._refund.entity.Refund; -import poomasi.domain.product.entity.Product; - -import java.io.Serializable; -import java.math.BigDecimal; - -@Entity -@Table(name = "order_product_details") -@Getter -@NoArgsConstructor -public class OrderProductDetails implements Serializable { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "order_product_details_id") - private Long id; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "product_id") - private Product product; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "order_id") - private Order order; - - /* @OneToOne(fetch = FetchType.LAZY) - private Refund refund;*/ - - @Column(name = "product_description", nullable = true) - private String productDescription; - - @Column(name = "product_name", length = 255) - private String productName; - - @Description("구매 당시 1개당 가격") - private BigDecimal price; - - @Column(name="count") - private Integer count; - - @Description("송장 번호") - @Column(name = "invoice_number") - private String invoiceNumber; - - //private String sellerName; - //private OneToMany Review; - //refund.. - - @Builder - public OrderProductDetails(Product product, Order order, String productDescription, String productName, BigDecimal price, Integer count) { - this.product = product; - this.order = order; - this.productDescription = productDescription; - this.productName = productName; - this.price = price; - this.count = count; - } - - public void setInvoiceNumber(String invoiceNumber) { - this.invoiceNumber = invoiceNumber; - } -} - diff --git a/src/main/java/poomasi/domain/order/entity/OrderStatus.java b/src/main/java/poomasi/domain/order/entity/OrderStatus.java index 6a3b07a8..2e71f97e 100644 --- a/src/main/java/poomasi/domain/order/entity/OrderStatus.java +++ b/src/main/java/poomasi/domain/order/entity/OrderStatus.java @@ -3,9 +3,6 @@ public enum OrderStatus { PENDING, // 결제 대기 중 AWAITING_SELLER_CONFIRMATION, // 판매자 확인 대기 중 - READY_FOR_SHIPMENT, // 배송 대기 중 - IN_TRANSIT, // 배송 중 - DELIVERED, // 배송 완료 - ORDER_COMPLETE // 주문 완료 + SELLER_CONFIRMED // 판매자 확인 완료 ; } \ No newline at end of file diff --git a/src/main/java/poomasi/domain/order/entity/_abstract/AbstractOrder.java b/src/main/java/poomasi/domain/order/entity/_abstract/AbstractOrder.java new file mode 100644 index 00000000..4b784690 --- /dev/null +++ b/src/main/java/poomasi/domain/order/entity/_abstract/AbstractOrder.java @@ -0,0 +1,88 @@ +package poomasi.domain.order.entity._abstract; + +import jakarta.persistence.*; +import jdk.jfr.Description; +import jdk.jfr.Timestamp; +import lombok.Getter; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.SQLDelete; +import org.hibernate.annotations.UpdateTimestamp; +import poomasi.domain.member.entity.Member; +import poomasi.domain.order._payment.entity.Payment; +import poomasi.domain.order.entity.OrderStatus; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.Date; + +@MappedSuperclass +@Getter +public abstract class AbstractOrder { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @JoinColumn(name = "member_id") + @ManyToOne(fetch = FetchType.LAZY) + @Description("주문 한 사람을 참조한다.") + private Member member; + + @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) + private Payment payment; + + @Column(name = "merchant_uid") + @Description("서버 내부 주문 id(아임포트 id)") + private String merchantUid = "p" + new Date().getTime(); + + @Column(name = "imp_uid") + @Description("아임포트 결제 imp_uid") + private String impUid; + + @Column(name = "created_at") + @CreationTimestamp + private LocalDateTime createdAt = LocalDateTime.now(); + + @Column(name = "updated_at") + @UpdateTimestamp + private LocalDateTime updateAt = LocalDateTime.now(); + + @Column(name = "deleted_at") + @Timestamp + private LocalDateTime deletedAt; + + @Column(name = "total_amount") + @Description("총 결제 금액") + private BigDecimal totalAmount; + + @Enumerated(EnumType.STRING) + private OrderStatus orderStatus = OrderStatus.PENDING; + + @Description("checksum") + private BigDecimal checksum; + + public void setCheckSum(BigDecimal checksum) { + this.checksum = checksum; + } + + public void reduceChecksum(BigDecimal amount) { + checksum = checksum.subtract(amount); + } + public void setTotalAmount(BigDecimal totalAmount) { + this.totalAmount = totalAmount; + } + + public void setOrderStatus(OrderStatus orderStatus) { + this.orderStatus = orderStatus; + } + + public void setImpUid(String impUid) { + this.impUid = impUid; + } + + public void setMerchantUid(String merchantUid) { + this.merchantUid = merchantUid; + } + +} + diff --git a/src/main/java/poomasi/domain/order/entity/_farm/FarmOrder.java b/src/main/java/poomasi/domain/order/entity/_farm/FarmOrder.java new file mode 100644 index 00000000..1de8705c --- /dev/null +++ b/src/main/java/poomasi/domain/order/entity/_farm/FarmOrder.java @@ -0,0 +1,34 @@ +package poomasi.domain.order.entity._farm; + +import jakarta.persistence.*; +import org.hibernate.annotations.Comment; +import poomasi.domain.order._payment.entity.Payment; +import poomasi.domain.order.entity._abstract.AbstractOrder; + +//@Entity +//@Table(name = "farm_order") +public class FarmOrder extends AbstractOrder { + /* + @OneToOne(fetch=FetchType.LAZY) + private FarmOrderDetails farmOrderDetails; + + @Column(name = "owner_id") + private Long ownerId; + + @Comment("농장 간단 설명") + private String description; + + @Comment("도로명 주소") + private String address; + + @Comment("상세 주소") + private String addressDetail; + + @Comment("위도") + private Double latitude; + + @Comment("경도") + private Double longitude; + */ +} + diff --git a/src/main/java/poomasi/domain/order/entity/_farm/FarmOrderDetails.java b/src/main/java/poomasi/domain/order/entity/_farm/FarmOrderDetails.java new file mode 100644 index 00000000..7b6e194c --- /dev/null +++ b/src/main/java/poomasi/domain/order/entity/_farm/FarmOrderDetails.java @@ -0,0 +1,28 @@ +package poomasi.domain.order.entity._farm; + +import jakarta.persistence.*; +import poomasi.domain.farm.entity.Farm; + +//@Entity +//@Table(name="farm_order_details") +public class FarmOrderDetails { + /* + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "farm_order_details_id") + private Long id; + + @OneToOne + private FarmOrder farmOrder; + + + @Column(name="farm_name") + private String farmName; + + @Column(name="farm_address") + private String farmAddress; + + +*/ + +} diff --git a/src/main/java/poomasi/domain/order/entity/_farm/OrderedFarm.java b/src/main/java/poomasi/domain/order/entity/_farm/OrderedFarm.java new file mode 100644 index 00000000..1c785e39 --- /dev/null +++ b/src/main/java/poomasi/domain/order/entity/_farm/OrderedFarm.java @@ -0,0 +1,20 @@ +package poomasi.domain.order.entity._farm; + + +import jakarta.persistence.*; +import poomasi.domain.farm.entity.Farm; + +@Entity +@Table(name = "ordered_farm") +public class OrderedFarm { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "ordered_farm_id") + private Long id; + + @OneToOne + private Farm farm; + + +} diff --git a/src/main/java/poomasi/domain/order/entity/_product/OrderedProduct.java b/src/main/java/poomasi/domain/order/entity/_product/OrderedProduct.java new file mode 100644 index 00000000..d495a281 --- /dev/null +++ b/src/main/java/poomasi/domain/order/entity/_product/OrderedProduct.java @@ -0,0 +1,90 @@ +package poomasi.domain.order.entity._product; + + +import jakarta.persistence.*; +import jdk.jfr.Description; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import poomasi.domain.order._aftersales.entity._product.ProductAfterSalesDetail; +import poomasi.domain.product.entity.Product; + +import java.io.Serializable; +import java.math.BigDecimal; + +@Entity +@Table(name = "ordered_products") +@Getter +@NoArgsConstructor +public class OrderedProduct implements Serializable { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "ordered_product_id") + private Long id; + + @OneToOne(fetch = FetchType.LAZY) + @JoinColumn(nullable = true, name = "product_after_sales_detail_id") + private ProductAfterSalesDetail productAfterSalesDetail; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "product_order_id") + private ProductOrder productOrder; + + + //FIXME : store Id를 참조해야 한다. + //나중에 store Id로 변경해야 한다 + private Long storeId; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "product_id") + private Product product; + + + + @Column(name = "product_description", nullable = true) + private String productDescription; + + @Column(name = "product_name", length = 255) + private String productName; + + @Description("구매 당시 1개당 가격") + private BigDecimal price; + + @Column(name="count") + private Integer count; + + @Description("송장 번호") + @Column(name = "invoice_number", nullable = true) + private String invoiceNumber; + + private ShippingStatus shippingStatus = ShippingStatus.ORDERED; + + // 웹훅 받아서 조회해야 함. + // findByInvoiceNumber 후 + // web hook controller 만들어서 + // 배송 상태 적절히 변경해야 함 + + @Builder + public OrderedProduct(Product product, ProductOrder productOrder, String productDescription, String productName, BigDecimal price, Integer count) { + this.product = product; + this.productOrder = productOrder; + this.productDescription = productDescription; + this.productName = productName; + this.price = price; + this.count = count; + } + + public void setInvoiceNumber(String invoiceNumber) { + this.invoiceNumber = invoiceNumber; + } + + public void setShippingStatus(ShippingStatus shippingStatus) { + this.shippingStatus = shippingStatus; + } + + public Long getOrderId(){ + return this.productOrder.getId(); + } +} + diff --git a/src/main/java/poomasi/domain/order/entity/_product/ProductOrder.java b/src/main/java/poomasi/domain/order/entity/_product/ProductOrder.java new file mode 100644 index 00000000..5618b63c --- /dev/null +++ b/src/main/java/poomasi/domain/order/entity/_product/ProductOrder.java @@ -0,0 +1,44 @@ +package poomasi.domain.order.entity._product; + + +import jakarta.persistence.*; +import jdk.jfr.Description; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.SQLDelete; +import poomasi.domain.order._aftersales.entity._product.ProductAfterSales; +import poomasi.domain.order.entity._abstract.AbstractOrder; + +import java.util.List; + +@Entity +@Table(name = "product_order") +@Getter +@NoArgsConstructor +@SQLDelete(sql = "UPDATE product_order SET deleted_at=current_timestamp WHERE id = ?") +public class ProductOrder extends AbstractOrder { + + @Column(name = "ordered_products_id") + @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) + private List orderedProducts; + + @OneToOne + @JoinColumn(name = "product_order_details_id") // 외래 키 지정 + @Description("상품 배송지, 요청 사항") + private ProductOrderDetails productOrderDetails; + + + @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) + @JoinColumn(name = "product_after_sales_id") + @Description("결제 완료 후 취소/교환/환불 관리 객체") + private ProductAfterSales productAfterSales; + + public ProductOrder(ProductOrderDetails productOrderDetails) { + this.productOrderDetails = productOrderDetails; + } + + public void addOrderedProduct(OrderedProduct orderedProduct) { + this.orderedProducts.add(orderedProduct); + } + +} diff --git a/src/main/java/poomasi/domain/order/entity/OrderDetails.java b/src/main/java/poomasi/domain/order/entity/_product/ProductOrderDetails.java similarity index 64% rename from src/main/java/poomasi/domain/order/entity/OrderDetails.java rename to src/main/java/poomasi/domain/order/entity/_product/ProductOrderDetails.java index 5bb7ed09..4d8fb903 100644 --- a/src/main/java/poomasi/domain/order/entity/OrderDetails.java +++ b/src/main/java/poomasi/domain/order/entity/_product/ProductOrderDetails.java @@ -1,4 +1,4 @@ -package poomasi.domain.order.entity; +package poomasi.domain.order.entity._product; import jakarta.persistence.*; import jdk.jfr.Description; @@ -6,17 +6,17 @@ import lombok.NoArgsConstructor; @Entity -@Table(name="order_details") +@Table(name="product_order_details") @Getter @NoArgsConstructor -public class OrderDetails { +public class ProductOrderDetails { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @OneToOne(mappedBy = "orderDetails") - private Order order; + @OneToOne(mappedBy = "productOrderDetails", cascade = CascadeType.ALL) // 필드명으로 지정 + private ProductOrder productOrder; @Column(name = "address") private String address; @@ -28,7 +28,7 @@ public class OrderDetails { @Column(name = "delivery_request", length = 255) private String deliveryRequest; - public OrderDetails(String address, String addressDetail, String deliveryRequest) { + public ProductOrderDetails(String address, String addressDetail, String deliveryRequest) { this.address = address; this.addressDetail = addressDetail; this.deliveryRequest = deliveryRequest; diff --git a/src/main/java/poomasi/domain/order/entity/_product/ProductsOrderDetailsStatus.java b/src/main/java/poomasi/domain/order/entity/_product/ProductsOrderDetailsStatus.java new file mode 100644 index 00000000..53a53fab --- /dev/null +++ b/src/main/java/poomasi/domain/order/entity/_product/ProductsOrderDetailsStatus.java @@ -0,0 +1,11 @@ +package poomasi.domain.order.entity._product; + +public enum ProductsOrderDetailsStatus { + WAITING_SHIPPING, + IN_SHIPPING, // 배송 중 + DELIVERED_COMPLETE, // 배송 완료 + CANCELLED, // 취소 + RETURNED, // 환불 + EXCHANGED // 교환 + ; +} diff --git a/src/main/java/poomasi/domain/order/entity/_product/ShippingStatus.java b/src/main/java/poomasi/domain/order/entity/_product/ShippingStatus.java new file mode 100644 index 00000000..e2a0a0aa --- /dev/null +++ b/src/main/java/poomasi/domain/order/entity/_product/ShippingStatus.java @@ -0,0 +1,8 @@ +package poomasi.domain.order.entity._product; + +public enum ShippingStatus { + ORDERED, // 주문 완료 (배송 전 시작 상태) -> 배송 보내기 전 단계. 판매자 확인 후 취소 가능 + SHIPMENT_STARTED, // 배송 시작 + IN_TRANSIT, // 배송 중 + DELIVERED // 배송 완료 +} diff --git a/src/main/java/poomasi/domain/order/repository/OrderProductDetailsRepository.java b/src/main/java/poomasi/domain/order/repository/OrderProductDetailsRepository.java deleted file mode 100644 index 61432033..00000000 --- a/src/main/java/poomasi/domain/order/repository/OrderProductDetailsRepository.java +++ /dev/null @@ -1,10 +0,0 @@ -package poomasi.domain.order.repository; - -import org.springframework.data.jpa.repository.JpaRepository; -import poomasi.domain.order.entity.OrderProductDetails; - -import java.util.List; - -public interface OrderProductDetailsRepository extends JpaRepository { - List findByOrderId(Long orderId); -} diff --git a/src/main/java/poomasi/domain/order/repository/OrderRepository.java b/src/main/java/poomasi/domain/order/repository/OrderRepository.java deleted file mode 100644 index f2476ad0..00000000 --- a/src/main/java/poomasi/domain/order/repository/OrderRepository.java +++ /dev/null @@ -1,14 +0,0 @@ -package poomasi.domain.order.repository; - -import org.springframework.data.jpa.repository.JpaRepository; -import poomasi.domain.order.entity.Order; -import poomasi.domain.order.entity.OrderProductDetails; - -import java.util.List; -import java.util.Optional; - -public interface OrderRepository extends JpaRepository { - List findByMemberId(Long memberId); - //List findById(Long id); - Optional findByMerchantUid(String merchantUid); -} diff --git a/src/main/java/poomasi/domain/order/repository/OrderedProductRepository.java b/src/main/java/poomasi/domain/order/repository/OrderedProductRepository.java new file mode 100644 index 00000000..4827c4de --- /dev/null +++ b/src/main/java/poomasi/domain/order/repository/OrderedProductRepository.java @@ -0,0 +1,9 @@ +package poomasi.domain.order.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import poomasi.domain.order.entity._product.OrderedProduct; + +import java.util.List; + +public interface OrderedProductRepository extends JpaRepository { +} diff --git a/src/main/java/poomasi/domain/order/repository/ProductOrderRepository.java b/src/main/java/poomasi/domain/order/repository/ProductOrderRepository.java new file mode 100644 index 00000000..9f0ba5a1 --- /dev/null +++ b/src/main/java/poomasi/domain/order/repository/ProductOrderRepository.java @@ -0,0 +1,15 @@ +package poomasi.domain.order.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import poomasi.domain.order.entity._product.ProductOrder; + +import java.util.List; +import java.util.Optional; + +public interface ProductOrderRepository extends JpaRepository { + List findByMemberId(Long memberId); + //List findById(Long id); + Optional findByMerchantUid(String merchantUid); + Optional findByImpUid(String impUid); + Optional findByMerchantUidAndImpUid(String merchantUid, String impUid); +} diff --git a/src/main/java/poomasi/domain/order/service/OrderService.java b/src/main/java/poomasi/domain/order/service/OrderService.java index 24175672..4d34b046 100644 --- a/src/main/java/poomasi/domain/order/service/OrderService.java +++ b/src/main/java/poomasi/domain/order/service/OrderService.java @@ -10,17 +10,16 @@ import poomasi.domain.auth.security.userdetail.UserDetailsImpl; import poomasi.domain.member.entity.Member; import poomasi.domain.order._payment.dto.request.PaymentPreRegisterRequest; -import poomasi.domain.order._payment.repository.PaymentRepository; -import poomasi.domain.order._payment.service.PaymentService; +import poomasi.domain.order.dto.request.OrderRegisterRequest; import poomasi.domain.order.dto.response.OrderDetailsResponse; import poomasi.domain.order.dto.response.OrderProductDetailsResponse; import poomasi.domain.order.dto.response.OrderResponse; -import poomasi.domain.order.entity.Order; -import poomasi.domain.order.entity.OrderDetails; -import poomasi.domain.order.entity.OrderProductDetails; +import poomasi.domain.order.entity._product.OrderedProduct; +import poomasi.domain.order.entity._product.ProductOrder; +import poomasi.domain.order.entity._product.ProductOrderDetails; import poomasi.domain.order.entity.OrderStatus; -import poomasi.domain.order.repository.OrderProductDetailsRepository; -import poomasi.domain.order.repository.OrderRepository; +import poomasi.domain.order.repository.OrderedProductRepository; +import poomasi.domain.order.repository.ProductOrderRepository; import poomasi.domain.product._cart.entity.Cart; import poomasi.domain.product._cart.repository.CartRepository; import poomasi.domain.product.entity.Product; @@ -31,7 +30,6 @@ import java.util.List; import java.util.stream.Collectors; -import static poomasi.domain.order.entity.OrderStatus.AWAITING_SELLER_CONFIRMATION; import static poomasi.global.error.BusinessError.*; @RequiredArgsConstructor @@ -39,26 +37,23 @@ @Slf4j public class OrderService { - private final OrderRepository orderRepository; + private final ProductOrderRepository productOrderRepository; private final CartRepository cartRepository; private final ProductRepository productRepository; - private final PaymentRepository paymentRepository; - private final PaymentService paymentService; - private final OrderProductDetailsRepository orderProductDetailsRepository; + private final OrderedProductRepository orderedProductRepository; @Transactional - public PaymentPreRegisterRequest preOrderRegister(){ + public PaymentPreRegisterRequest productPreOrderRegister(OrderRegisterRequest orderRegisterRequest){ Member member = getMember(); Long memberId = member.getId(); List cartList = cartRepository.findByMemberIdAndSelected(memberId); - //TODO : dto에서 address, address detail 꺼내와야 함. -> dto 확정이 안 나서 임시로 넣음 - String address = "금정구"; - String addressDetails = "수림로"; - String deliveryRequest = "조심히 다뤄 주세요"; + String address = orderRegisterRequest.address(); + String addressDetails = orderRegisterRequest.addressDetails(); + String deliveryRequest = orderRegisterRequest.deliveryRequest(); - Order order = new Order( - new OrderDetails(address, + ProductOrder productOrder = new ProductOrder( + new ProductOrderDetails(address, addressDetails, deliveryRequest) ); @@ -66,40 +61,61 @@ public PaymentPreRegisterRequest preOrderRegister(){ //cart에 있는 총 가격 계산하기 BigDecimal totalPrice = BigDecimal.ZERO; - // cart 돌면서 order details 추가 + // cart 돌면서 productOrder details 추가 for (Cart cart : cartList) { Long productId = cart.getProductId(); Product product = productRepository.findById(productId) .orElseThrow(() -> new BusinessException(PRODUCT_NOT_FOUND)); + + Integer productStock = product.getStock(); + Integer quantityInCart = cart.getCount(); + + // 현재 남아있는 재고보다 더 많이 요청하면 + if(quantityInCart > productStock){ + throw new BusinessException(PRODUCT_STOCK_ZERO); + } + String productDescription = product.getDescription(); Integer count = cart.getCount(); String productName = product.getName(); BigDecimal price = BigDecimal.valueOf(product.getPrice()); - OrderProductDetails orderProductDetails = OrderProductDetails + OrderedProduct orderedProduct = OrderedProduct .builder() .product(product) - .order(order) + .productOrder(productOrder) .productDescription(productDescription) .productName(productName) .price(price) .count(count) .build(); - order.addOrderDetail(orderProductDetails); + productOrder.addOrderedProduct(orderedProduct); totalPrice = totalPrice.add(price); } - order.setTotalAmount(totalPrice); - orderRepository.save(order); + productOrder.setTotalAmount(totalPrice); + productOrder.setCheckSum(totalPrice); + productOrderRepository.save(productOrder); - String merchantUid = order.getMerchantUid(); + String merchantUid = productOrder.getMerchantUid(); return new PaymentPreRegisterRequest(merchantUid, totalPrice); } + @Transactional + //TODO : 만들어야 합니다 ~ + public PaymentPreRegisterRequest farmPreOrderRegister(){ + Member member = getMember(); + String merchantUid = ""; + BigDecimal totalPrice = BigDecimal.ZERO; + + return new PaymentPreRegisterRequest(merchantUid, totalPrice); + } + + @Description("멤버 ID 기반으로 모든 order 다 들고 오는 메서드") public List findAllOrdersByMemberId(){ Member member = getMember(); Long memberId = member.getId(); - List orderList = orderRepository.findByMemberId(memberId); - return orderList + List productOrderList = productOrderRepository.findByMemberId(memberId); + return productOrderList .stream() .map(OrderResponse::fromEntity) .collect(Collectors.toList() @@ -109,30 +125,32 @@ public List findAllOrdersByMemberId(){ @Description("멤버 id 기반으로 특정 orderId 들고오는 메서드") public OrderResponse findOrderByMemberId(Long orderId){ Member member = getMember(); - Order order = orderRepository.findById(orderId) + ProductOrder productOrder = productOrderRepository.findById(orderId) .orElseThrow(()-> new BusinessException(ORDER_NOT_FOUND)); - validateOrderOwnership(order, member); - return OrderResponse.fromEntity(order); + validateOrderOwnership(productOrder, member); + return OrderResponse.fromEntity(productOrder); } @Description("orderId 기반으로 order details(주소, 상세주소, 배송 요청 사항 ..등) 들고오는 메서드") public OrderDetailsResponse findOrderDetailsByOrderId(Long orderId){ - Order order = orderRepository.findById(orderId) + ProductOrder productOrder = productOrderRepository.findById(orderId) .orElseThrow(()-> new BusinessException(ORDER_NOT_FOUND)); - OrderDetails orderDetails = order.getOrderDetails(); + ProductOrderDetails productOrderDetails = productOrder.getProductOrderDetails(); - return OrderDetailsResponse.fromEntity(orderDetails); + return OrderDetailsResponse.fromEntity(productOrderDetails); } + + @Description("orderId에 해당하는 order product details 가져오는 메서드") public List findAllOrderProductDetails(Long orderId){ Member member = getMember(); - Order order = orderRepository.findById(orderId) + ProductOrder productOrder = productOrderRepository.findById(orderId) .orElseThrow(()-> new BusinessException(ORDER_NOT_FOUND)); - validateOrderOwnership(order, member); - return order.getOrderProductDetails() + validateOrderOwnership(productOrder, member); + return productOrder.getOrderedProducts() .stream() .map(OrderProductDetailsResponse::fromEntity) .collect(Collectors.toList() @@ -143,44 +161,46 @@ public List findAllOrderProductDetails(Long orderId @Description("orderId에 해당하는 order product Details의 단건 조회") public OrderProductDetailsResponse findOrderProductDetailsById(Long orderId, Long orderProductDetailsId){ Member member = getMember(); - OrderProductDetails orderProductDetails = orderProductDetailsRepository.findById(orderProductDetailsId) + OrderedProduct orderedProduct = orderedProductRepository.findById(orderProductDetailsId) .orElseThrow(()-> new BusinessException(ORDER_PRODUCT_DETAILS_NOT_FOUND)); - Order order = orderProductDetails.getOrder(); + ProductOrder productOrder = orderedProduct.getProductOrder(); - // order product details의 주인 order 검사 그리고 , orderId의 주인 member 검사 - validateOrderProductDetailsByOrderId(order, orderId); - validateOrderOwnership(order, member); + // productOrder product details의 주인 productOrder 검사 그리고 , orderId의 주인 member 검사 + validateOrderProductDetailsByOrderId(productOrder, orderId); + validateOrderOwnership(productOrder, member); - return OrderProductDetailsResponse.fromEntity(orderProductDetails); + return OrderProductDetailsResponse.fromEntity(orderedProduct); } @Description("member의 order인지 검사하는 메서드") - private void validateOrderOwnership(Order order, Member member) { - if (!order.getMember().getId().equals(member.getId())) { + private void validateOrderOwnership(ProductOrder productOrder, Member member) { + if (!productOrder.getMember().getId().equals(member.getId())) { throw new BusinessException(ORDER_NOT_OWNED_EXCEPTION); } } - @Description("orderId에 해당하는 order Product Details인지 조회하는 메서드") - private void validateOrderProductDetailsByOrderId(Order order, Long orderId) { - if(order.getId()!=orderId){ + @Description("orderId에 해당하는 productOrder Product Details인지 조회하는 메서드") + private void validateOrderProductDetailsByOrderId(ProductOrder productOrder, Long orderId) { + if(productOrder.getId()!=orderId){ throw new BusinessException(ORDER_PRODUCT_DETAILS_NOT_OWNED_EXCEPTION); } } + + /* @Description("결제가 완료 된 후, 주문 상태 변경하는 메서드. 굳이 없어도 되긴 함.") private void completePaymentAndUpdateStatus(Long orderId){ - Order order = orderRepository.findById(orderId) + ProductOrder order = productOrderRepository.findById(orderId) .orElseThrow(()-> new BusinessException(ORDER_NOT_FOUND)); order.setOrderStatus(AWAITING_SELLER_CONFIRMATION); - } + }*/ @Description("주문 상태를 변경하는 메서드") private void changeOrderStatus(Long orderId, OrderStatus orderStatus){ - Order order = orderRepository.findById(orderId) + ProductOrder productOrder = productOrderRepository.findById(orderId) .orElseThrow(()-> new BusinessException(ORDER_NOT_FOUND)); - order.setOrderStatus(orderStatus); + productOrder.setOrderStatus(orderStatus); } diff --git a/src/main/java/poomasi/domain/product/entity/Product.java b/src/main/java/poomasi/domain/product/entity/Product.java index 8c70ba51..cd80bfbc 100644 --- a/src/main/java/poomasi/domain/product/entity/Product.java +++ b/src/main/java/poomasi/domain/product/entity/Product.java @@ -1,19 +1,7 @@ package poomasi.domain.product.entity; -import jakarta.persistence.CascadeType; -import jakarta.persistence.CollectionTable; -import jakarta.persistence.Column; -import jakarta.persistence.ElementCollection; -import jakarta.persistence.Entity; -import jakarta.persistence.EnumType; -import jakarta.persistence.Enumerated; -import jakarta.persistence.FetchType; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.ManyToOne; -import jakarta.persistence.OneToMany; +import jakarta.persistence.*; + import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; @@ -23,7 +11,7 @@ import org.hibernate.annotations.Comment; import org.hibernate.annotations.CreationTimestamp; import org.hibernate.annotations.UpdateTimestamp; -import poomasi.domain.order.entity.OrderProductDetails; +import poomasi.domain.order.entity._product.OrderedProduct; import poomasi.domain.store.entity.Store; import poomasi.domain.product.dto.ProductRegisterRequest; import poomasi.domain.review.entity.Review; @@ -87,7 +75,7 @@ public class Product { @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) @JoinColumn(name = "order_product_details_id") - private List orderProductDetails; + private List orderProductDetails; @Builder @@ -125,4 +113,17 @@ public void addStock(Integer stock) { this.stock += stock; } + public void addReview(Review pReview) { + this.reviewList.add(pReview); + this.averageRating = reviewList.stream() + .mapToDouble(Review::getRating) // 각 리뷰의 평점을 double로 변환 + .average() // 평균 계산 + .orElse(0.0); + } + + public void subtractStock(Integer stock) { + this.stock -= stock; + } + + } diff --git a/src/main/java/poomasi/global/error/BusinessError.java b/src/main/java/poomasi/global/error/BusinessError.java index 67d668d4..b177035e 100644 --- a/src/main/java/poomasi/global/error/BusinessError.java +++ b/src/main/java/poomasi/global/error/BusinessError.java @@ -1,5 +1,7 @@ package poomasi.global.error; +import org.springframework.http.HttpStatus; + import lombok.AllArgsConstructor; import lombok.Getter; import org.springframework.http.HttpStatus; @@ -10,6 +12,7 @@ public enum BusinessError { // Product PRODUCT_NOT_FOUND(HttpStatus.NOT_FOUND, "상품을 찾을 수 없습니다."), PRODUCT_STOCK_ZERO(HttpStatus.BAD_REQUEST, "재고가 없습니다."), + STOCK_QUANTITY_EXCEEDED(HttpStatus.BAD_REQUEST, "장바구나 수량이 남은 재고를 초과하였습니다"), // Category CATEGORY_NOT_FOUND(HttpStatus.NOT_FOUND, "카테고리를 찾을 수 없습니다."), @@ -85,11 +88,17 @@ public enum BusinessError { // PAYMENT PAYMENT_NOT_FOUND(HttpStatus.NOT_FOUND, "결제를 찾을 수 없습니다."), PAYMENT_AMOUNT_MISMATCH(HttpStatus.BAD_REQUEST, "사전 결제 금액과 사후 결제 금액이 일치하지 않습니다."), + PAYMENT_BAD_REQUEST(HttpStatus.BAD_REQUEST, "잘못 된 결제 요청입니다."), //Store - STORE_NOT_FOUND(HttpStatus.NOT_FOUND, "등록된 상점이 없습니다."); + STORE_NOT_FOUND(HttpStatus.NOT_FOUND, "등록된 상점이 없습니다.") + + + + ; private final HttpStatus httpStatus; private final String message; } + diff --git a/src/main/java/poomasi/global/error/ExceptionAdvice.java b/src/main/java/poomasi/global/error/ExceptionAdvice.java index 5e5e536b..7dc73824 100644 --- a/src/main/java/poomasi/global/error/ExceptionAdvice.java +++ b/src/main/java/poomasi/global/error/ExceptionAdvice.java @@ -30,4 +30,15 @@ public ErrorResponse applicationExceptionHandler(ApplicationException exception) .title(exception.getClass().getSimpleName()) .build(); } + + @ExceptionHandler(PaymentConfirmException.class) + public ErrorResponse paymentConfirmExceptionHandler(PaymentConfirmException exception) { + PaymentConfirmError paymentConfirmError = exception.getPaymentConfirmError(); + log.error("[{}] : {}", paymentConfirmError.name(), paymentConfirmError.getReason()); + return ErrorResponse + .builder(exception, paymentConfirmError.getHttpStatus(), paymentConfirmError.getReason()) + .title(paymentConfirmError.name()) + .build(); + } + } diff --git a/src/main/java/poomasi/global/error/PaymentConfirmError.java b/src/main/java/poomasi/global/error/PaymentConfirmError.java new file mode 100644 index 00000000..a04ddafb --- /dev/null +++ b/src/main/java/poomasi/global/error/PaymentConfirmError.java @@ -0,0 +1,19 @@ +package poomasi.global.error; + + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum PaymentConfirmError { + + PAYMENT_PROUCT_CONFIRM_EXCEPTION(HttpStatus.INTERNAL_SERVER_ERROR, "결제 직전 상품 수량 부족") + ; + + private final HttpStatus httpStatus; + private final String reason; + + +} diff --git a/src/main/java/poomasi/global/error/PaymentConfirmException.java b/src/main/java/poomasi/global/error/PaymentConfirmException.java new file mode 100644 index 00000000..ac03ed78 --- /dev/null +++ b/src/main/java/poomasi/global/error/PaymentConfirmException.java @@ -0,0 +1,12 @@ +package poomasi.global.error; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class PaymentConfirmException extends RuntimeException { + + private final PaymentConfirmError paymentConfirmError; + +}