Skip to content
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
a47b1ad
fix #98 : 응답이 없을때 response type을 void로 수정
hyeonji91 Oct 23, 2025
4f3ebab
feature #166 : 주문하기 controller단
hyeonji91 Oct 23, 2025
e719184
feature #166 : 주문하기 - 상품 확인
hyeonji91 Oct 23, 2025
f676c3e
feature #116 : 주문하기 - 재고 차감
hyeonji91 Oct 23, 2025
3ac8140
feature #166 : 쿠폰 유효성 검증 및 할인 금액 계산 로직 추가
hyeonji91 Oct 23, 2025
e2f9700
feature #166 : 쿠폰 사용 처리 로직
hyeonji91 Oct 23, 2025
b7bdf24
feature #166 : tosspayments 결제 승인 API
hyeonji91 Oct 23, 2025
adf7302
feature #166 : 결제하기 API
hyeonji91 Oct 23, 2025
cd46b94
feature #166 : spring cloud version수정
hyeonji91 Oct 23, 2025
e95abbb
feature #166: 결제 실패시 재고 되돌리기
hyeonji91 Oct 23, 2025
ec9fc60
feature #166: 결제 완료 후 payment 업데이트
hyeonji91 Oct 23, 2025
5f614ac
Merge branch 'feature/#166-customer-order-api' of https://github.com/…
hyeonji91 Oct 24, 2025
3ec78a2
style #166 : pessimisticLock이름 변경
hyeonji91 Oct 24, 2025
20e471e
feature #166: 주문하기
hyeonji91 Oct 24, 2025
4080205
feature #166 : 쿠폰 롤백
hyeonji91 Oct 24, 2025
bd8f432
feature #166 : response에 가격 세분화 (상품 총 가격, 실 결제 금액, 할인 금액)
hyeonji91 Oct 24, 2025
e3adbc9
style #166 : spotless적용
hyeonji91 Oct 24, 2025
b041776
feature #166 : tosspaymentkey, tossorderId
hyeonji91 Oct 24, 2025
1d25dfa
feature #166 : payment method추가
hyeonji91 Oct 24, 2025
9dada18
Merge branch 'develop' into feature/#166-customer-order-api
hyeonji91 Oct 24, 2025
a955730
fix #187 : 기본 배송비 배송비 정책에서 가져오기
hyeonji91 Oct 24, 2025
f2a0308
feature #166 : 환경변수 주입
hyeonji91 Oct 24, 2025
4205911
fix #187 : 코드 리뷰 반영
hyeonji91 Oct 25, 2025
275499e
refactor #187 : 재고 보상트랜젝션 중 N+1 이슈 해결
hyeonji91 Oct 25, 2025
ab70c78
refactor #187 : 주문하기 API - 쿼리 조회 횟수 감소
hyeonji91 Oct 25, 2025
8e737ff
refactor #166 : tosspayment 호출 로직 비즈니스 로직과 분리
hyeonji91 Oct 25, 2025
ae40ee7
style #187 : spotless적용
hyeonji91 Oct 25, 2025
829143e
feature #166 : preparePayment 호출 전 결제 취소 시나리오 대응 - 스케줄러
hyeonji91 Oct 25, 2025
c407e7c
feature #187 : 물품 수량 음수 방지 - request validaton 추가
hyeonji91 Oct 27, 2025
b162bd0
feature #187 : 물품 옵션 값 repository - @param추가
hyeonji91 Oct 27, 2025
eea163a
style : spotless 적용
hyeonji91 Oct 27, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,17 @@ clean {
delete querydslDirProperty
}

//feign client를 위한 springcloud
ext {
set('springCloudVersion', "2025.0.0")
}

dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
}
}

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'

Expand All @@ -76,6 +87,10 @@ dependencies {
annotationProcessor "com.querydsl:querydsl-apt::jakarta"
annotationProcessor "jakarta.persistence:jakarta.persistence-api"

//feign client
implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
implementation group: 'io.github.openfeign', name: 'feign-gson', version: '11.0'

compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/com/irum/come2us/Come2usApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableFeignClients
@EnableScheduling
public class Come2usApplication {

public static void main(String[] args) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.irum.come2us.domain.coupon.application.service;

import com.irum.come2us.domain.coupon.domain.entity.AppliedCoupon;
import com.irum.come2us.domain.coupon.domain.entity.Coupon;
import com.irum.come2us.domain.coupon.domain.repository.AppliedCouponRepository;
import com.irum.come2us.domain.coupon.domain.repository.CouponRepository;
import com.irum.come2us.domain.payment.domain.entity.Payment;
import java.util.List;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
public class AppliedCouponService {
private final AppliedCouponRepository appliedCouponRepository;
private final CouponRepository couponRepository;

/** 쿠폰 사용 처리 */
public void createAppliedCouponList(Payment payment, List<UUID> couponIdList) {
List<Coupon> couponList = couponRepository.findAllById(couponIdList);

List<AppliedCoupon> appliedCouponList =
couponList.stream()
.map(
coupon ->
AppliedCoupon.builder()
.payment(payment)
.coupon(coupon)
.build())
.toList();

appliedCouponRepository.saveAll(appliedCouponList);
}

/** 롤백 */
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void rollbackAppliedCouponList(Payment payment) {
appliedCouponRepository.deleteByPayment(payment);
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package com.irum.come2us.domain.coupon.application.service;

import com.irum.come2us.domain.coupon.domain.entity.Coupon;
import com.irum.come2us.domain.coupon.domain.repository.AppliedCouponRepository;
import com.irum.come2us.domain.coupon.domain.repository.CouponRepository;
import com.irum.come2us.domain.coupon.presentation.dto.request.CouponGenerateRequest;
import com.irum.come2us.domain.member.domain.entity.Member;
import com.irum.come2us.domain.member.domain.repository.MemberRepository;
import com.irum.come2us.global.presentation.advice.exception.CommonException;
import com.irum.come2us.global.presentation.advice.exception.errorcode.CouponErrorCode;
import com.irum.come2us.global.presentation.advice.exception.errorcode.MemberErrorCode;
import java.time.LocalDateTime;
import java.util.List;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
Expand All @@ -20,6 +22,7 @@
public class CouponService {
private final CouponRepository couponRepository;
private final MemberRepository memberRepository;
private final AppliedCouponRepository appliedCouponRepository;

public void createCoupon(CouponGenerateRequest request, Long memberId) {

Expand Down Expand Up @@ -51,4 +54,36 @@ public void deleteCoupon(UUID couponId, Long memberId) {
}
couponRepository.delete(coupon);
}

/** 쿠폰 유효성 검증 및 할인 금액 계산 */
public int validAndCalCoupon(List<UUID> couponIdList, int calculatedTotalPrice, Member member) {
if (couponIdList.isEmpty()) {
return 0;
}

int totalDiscount = 0;
List<Coupon> couponList = couponRepository.findAllById(couponIdList);

for (Coupon coupon : couponList) {
// 권한 검사
if (!coupon.getMember().getMemberId().equals(member.getMemberId())) {
throw new CommonException(CouponErrorCode.COUPON_NO_PERMISSION);
}
// 만료일 검사
if (coupon.getExpiration().isBefore(LocalDateTime.now())) {
throw new CommonException(CouponErrorCode.COUPON_EXPIRATION);
}
// 사용 여부 검사
if (appliedCouponRepository.existsByCouponId(coupon.getId())) {
throw new CommonException(CouponErrorCode.COUPON_ALREADY_USED);
}
totalDiscount += coupon.getDiscountAmount();
}

if (totalDiscount > calculatedTotalPrice) {
totalDiscount = calculatedTotalPrice;
}

return totalDiscount;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import jakarta.persistence.*;
import java.util.UUID;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.SQLDelete;
Expand All @@ -16,6 +18,8 @@
@Entity
@Table(name = "p_applied_coupon")
@Getter
@Builder
@AllArgsConstructor
@SQLDelete(sql = "UPDATE p_applied_coupon SET deleted_at = NOW() WHERE applied_coupon_id = ?")
@Where(clause = "deleted_at IS NULL")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
package com.irum.come2us.domain.coupon.domain.repository;

import com.irum.come2us.domain.coupon.domain.entity.AppliedCoupon;
import com.irum.come2us.domain.payment.domain.entity.Payment;
import java.util.List;
import java.util.UUID;
import org.springframework.data.jpa.repository.JpaRepository;

public interface AppliedCouponRepository extends JpaRepository<AppliedCoupon, UUID> {
List<AppliedCoupon> findByCouponId(UUID couponId);

boolean existsByCouponId(UUID couponId);

void deleteByPayment(Payment payment);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.irum.come2us.domain.order.application.mapper;

import com.irum.come2us.domain.order.domain.entity.Order;
import com.irum.come2us.domain.order.domain.entity.OrderDetail;
import com.irum.come2us.domain.order.presentation.dto.response.AddressResponse;
import com.irum.come2us.domain.order.presentation.dto.response.CustomerOrderResponse;
import java.util.List;
import org.springframework.stereotype.Component;

@Component
public class CustomerOrderMapper {

public static CustomerOrderResponse toCustomerOrderResponse(
Order order, List<OrderDetail> orderDetailList) {
List<CustomerOrderResponse.ProductSummary> productSummaryList =
orderDetailList.stream().map(CustomerOrderMapper::toProductSummary).toList();

return CustomerOrderResponse.builder()
.orderId(order.getOrderId())
.address(AddressResponse.from(order.getDeliveryAddress().getAddress()))
.totalProductPrice(order.getTotalPrice())
.totalDiscountAmount(order.getPayment().getTotalDiscountAmount())
.totalPaymentAmount(order.getPayment().getAmount())
.productList(productSummaryList)
.build();
}

private static CustomerOrderResponse.ProductSummary toProductSummary(OrderDetail orderDetail) {
return CustomerOrderResponse.ProductSummary.builder()
.orderDetailId(orderDetail.getOrderDetailId())
.productName(orderDetail.getProductName())
.price(orderDetail.getPrice())
.quantity(orderDetail.getQuantity())
.build();
}
}
Loading