From dc7e867d7dab118a8000c80a0a1f861a2d7df739 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=A0=EA=B4=80=EA=B7=9C?= Date: Sun, 17 Aug 2025 21:18:27 +0900 Subject: [PATCH 01/23] =?UTF-8?q?feat:=20UserFixture=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../koin/domain/user/model/User.java | 13 ++++------ .../user/repository/UserRepository.java | 2 ++ .../acceptance/fixture/UserFixture.java | 26 +++++++++++++++++++ 3 files changed, 33 insertions(+), 8 deletions(-) create mode 100644 src/test/java/in/koreatech/payment/acceptance/fixture/UserFixture.java diff --git a/src/main/java/in/koreatech/koin/domain/user/model/User.java b/src/main/java/in/koreatech/koin/domain/user/model/User.java index e3597af..3abe225 100644 --- a/src/main/java/in/koreatech/koin/domain/user/model/User.java +++ b/src/main/java/in/koreatech/koin/domain/user/model/User.java @@ -3,20 +3,13 @@ import static jakarta.persistence.GenerationType.IDENTITY; import static lombok.AccessLevel.PROTECTED; -import java.time.LocalDateTime; - import org.hibernate.annotations.Where; -import in.koreatech.payment.common.model.BaseEntity; -import jakarta.persistence.Column; import jakarta.persistence.Entity; -import jakarta.persistence.EnumType; -import jakarta.persistence.Enumerated; import jakarta.persistence.GeneratedValue; import jakarta.persistence.Id; import jakarta.persistence.Table; -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Size; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @@ -31,4 +24,8 @@ public class User { @GeneratedValue(strategy = IDENTITY) private Integer id; + @Builder + public User(Integer id) { + this.id = id; + } } diff --git a/src/main/java/in/koreatech/koin/domain/user/repository/UserRepository.java b/src/main/java/in/koreatech/koin/domain/user/repository/UserRepository.java index ad430ac..dee329e 100644 --- a/src/main/java/in/koreatech/koin/domain/user/repository/UserRepository.java +++ b/src/main/java/in/koreatech/koin/domain/user/repository/UserRepository.java @@ -15,4 +15,6 @@ default User getById(Integer userId) { return findById(userId) .orElseThrow(() -> UserNotFoundException.withDetail("userId: " + userId)); } + + User save(User user); } diff --git a/src/test/java/in/koreatech/payment/acceptance/fixture/UserFixture.java b/src/test/java/in/koreatech/payment/acceptance/fixture/UserFixture.java new file mode 100644 index 0000000..6a1e14b --- /dev/null +++ b/src/test/java/in/koreatech/payment/acceptance/fixture/UserFixture.java @@ -0,0 +1,26 @@ +package in.koreatech.payment.acceptance.fixture; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import in.koreatech.koin.domain.user.model.User; +import in.koreatech.koin.domain.user.repository.UserRepository; + +@Component +@SuppressWarnings("NonAsciiCharacters") +public class UserFixture { + + private final UserRepository userRepository; + + @Autowired + public UserFixture(UserRepository userRepository) { + this.userRepository = userRepository; + } + + public User 코인_유저() { + return userRepository.save(User.builder() + .id(1) + .build() + ); + } +} From 6ff87e26b8f4816f446d4c8ba04f657cedb4e83d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=A0=EA=B4=80=EA=B7=9C?= Date: Sun, 17 Aug 2025 21:33:02 +0900 Subject: [PATCH 02/23] =?UTF-8?q?feat:=20JWT=20=ED=86=A0=ED=81=B0=20?= =?UTF-8?q?=EB=B0=9C=EA=B8=89=20=EB=A9=94=EC=86=8C=EB=93=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Jwt 클래스 네이밍 수정 - 테스트 메소드 추가 --- ...JwtTokenResolver.java => JwtProvider.java} | 31 ++++++++++++++++--- .../acceptance/fixture/UserFixture.java | 12 +++++-- 2 files changed, 36 insertions(+), 7 deletions(-) rename src/main/java/in/koreatech/payment/common/auth/{JwtTokenResolver.java => JwtProvider.java} (54%) diff --git a/src/main/java/in/koreatech/payment/common/auth/JwtTokenResolver.java b/src/main/java/in/koreatech/payment/common/auth/JwtProvider.java similarity index 54% rename from src/main/java/in/koreatech/payment/common/auth/JwtTokenResolver.java rename to src/main/java/in/koreatech/payment/common/auth/JwtProvider.java index d987123..2fb0a2f 100644 --- a/src/main/java/in/koreatech/payment/common/auth/JwtTokenResolver.java +++ b/src/main/java/in/koreatech/payment/common/auth/JwtProvider.java @@ -1,27 +1,50 @@ package in.koreatech.payment.common.auth; +import java.security.Key; +import java.time.Instant; import java.util.Base64; +import java.util.Date; import javax.crypto.SecretKey; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; +import in.koreatech.koin.domain.user.exception.UserNotFoundException; +import in.koreatech.koin.domain.user.model.User; import in.koreatech.payment.common.auth.exception.UnauthenticatedTokenException; -import in.koreatech.payment.common.exception.custom.AuthenticationException; import io.jsonwebtoken.JwtException; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.security.Keys; @Component -public class JwtTokenResolver { +public class JwtProvider { private final String secretKey; + private final Long expirationTime; - public JwtTokenResolver( - @Value("${jwt.secret-key}") String secretKey + public JwtProvider( + @Value("${jwt.secret-key}") String secretKey, + @Value("${jwt.access-token.expiration-time}") Long expirationTime ) { this.secretKey = secretKey; + this.expirationTime = expirationTime; + } + + public String createToken(User user) { + if (user == null) { + throw UserNotFoundException.withDetail("user : " + null); + } + Key key = getSecretKey(); + return Jwts.builder() + .signWith(key) + .header() + .add("typ", "JWT") + .add("alg", key.getAlgorithm()) + .and() + .claim("id", user.getId()) + .expiration(Date.from(Instant.now().plusMillis(expirationTime))) + .compact(); } public Integer getUserId(String token) { diff --git a/src/test/java/in/koreatech/payment/acceptance/fixture/UserFixture.java b/src/test/java/in/koreatech/payment/acceptance/fixture/UserFixture.java index 6a1e14b..4525b7f 100644 --- a/src/test/java/in/koreatech/payment/acceptance/fixture/UserFixture.java +++ b/src/test/java/in/koreatech/payment/acceptance/fixture/UserFixture.java @@ -1,10 +1,10 @@ package in.koreatech.payment.acceptance.fixture; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import in.koreatech.koin.domain.user.model.User; import in.koreatech.koin.domain.user.repository.UserRepository; +import in.koreatech.payment.common.auth.JwtProvider; @Component @SuppressWarnings("NonAsciiCharacters") @@ -12,9 +12,11 @@ public class UserFixture { private final UserRepository userRepository; - @Autowired - public UserFixture(UserRepository userRepository) { + private final JwtProvider jwtProvider; + + public UserFixture(UserRepository userRepository, JwtProvider jwtProvider) { this.userRepository = userRepository; + this.jwtProvider = jwtProvider; } public User 코인_유저() { @@ -23,4 +25,8 @@ public UserFixture(UserRepository userRepository) { .build() ); } + + public String getToken(User user) { + return jwtProvider.createToken(user); + } } From e5a34145bac26dd10beb20ba6c83d4a461e82aee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=A0=EA=B4=80=EA=B7=9C?= Date: Sun, 17 Aug 2025 21:33:19 +0900 Subject: [PATCH 03/23] =?UTF-8?q?fix:=20MockMvc=20=EC=A0=91=EA=B7=BC=20?= =?UTF-8?q?=EC=A0=9C=EC=96=B4=EC=9E=90=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/in/koreatech/payment/acceptance/AcceptanceTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/in/koreatech/payment/acceptance/AcceptanceTest.java b/src/test/java/in/koreatech/payment/acceptance/AcceptanceTest.java index 6f83511..d312399 100644 --- a/src/test/java/in/koreatech/payment/acceptance/AcceptanceTest.java +++ b/src/test/java/in/koreatech/payment/acceptance/AcceptanceTest.java @@ -30,7 +30,7 @@ public abstract class AcceptanceTest { private static final String ROOT_PASSWORD = "1234"; @Autowired - private MockMvc mockMvc; + public MockMvc mockMvc; @Autowired private DBInitializer dbInitializer; From ac3c045341bdc4beedf1c7ab1349a8cd00841316 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=A0=EA=B4=80=EA=B7=9C?= Date: Sun, 17 Aug 2025 21:36:38 +0900 Subject: [PATCH 04/23] =?UTF-8?q?feat:=20Cart=20Fixture=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../koin/domain/order/cart/model/Cart.java | 7 +++++ .../order/cart/repository/CartRepository.java | 2 ++ .../acceptance/fixture/CartFixture.java | 27 +++++++++++++++++++ 3 files changed, 36 insertions(+) create mode 100644 src/test/java/in/koreatech/payment/acceptance/fixture/CartFixture.java diff --git a/src/main/java/in/koreatech/koin/domain/order/cart/model/Cart.java b/src/main/java/in/koreatech/koin/domain/order/cart/model/Cart.java index 7ed1cd0..5b134c8 100644 --- a/src/main/java/in/koreatech/koin/domain/order/cart/model/Cart.java +++ b/src/main/java/in/koreatech/koin/domain/order/cart/model/Cart.java @@ -22,6 +22,7 @@ import jakarta.persistence.OneToMany; import jakarta.persistence.OneToOne; import jakarta.persistence.Table; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @@ -52,4 +53,10 @@ public Integer calculateItemsAmount() { .mapToInt(CartMenuItem::calculateTotalAmount) .sum(); } + + @Builder + public Cart(User user, OrderableShop orderableShop) { + this.user = user; + this.orderableShop = orderableShop; + } } diff --git a/src/main/java/in/koreatech/koin/domain/order/cart/repository/CartRepository.java b/src/main/java/in/koreatech/koin/domain/order/cart/repository/CartRepository.java index 34fc90e..1bc86e5 100644 --- a/src/main/java/in/koreatech/koin/domain/order/cart/repository/CartRepository.java +++ b/src/main/java/in/koreatech/koin/domain/order/cart/repository/CartRepository.java @@ -20,4 +20,6 @@ default Cart getCartByUserId(Integer userId) { } void deleteByUserId(Integer userId); + + Cart save(Cart cart); } diff --git a/src/test/java/in/koreatech/payment/acceptance/fixture/CartFixture.java b/src/test/java/in/koreatech/payment/acceptance/fixture/CartFixture.java new file mode 100644 index 0000000..c729854 --- /dev/null +++ b/src/test/java/in/koreatech/payment/acceptance/fixture/CartFixture.java @@ -0,0 +1,27 @@ +package in.koreatech.payment.acceptance.fixture; + +import org.springframework.stereotype.Component; + +import in.koreatech.koin.domain.order.cart.model.Cart; +import in.koreatech.koin.domain.order.cart.repository.CartRepository; +import in.koreatech.koin.domain.order.shop.model.entity.shop.OrderableShop; +import in.koreatech.koin.domain.user.model.User; + +@Component +@SuppressWarnings("NonAsciiCharacters") +public class CartFixture { + + private final CartRepository cartRepository; + + public CartFixture(CartRepository cartRepository) { + this.cartRepository = cartRepository; + } + + public Cart 장바구니(User user, OrderableShop orderableShop) { + return cartRepository.save(Cart.builder() + .user(user) + .orderableShop(orderableShop) + .build() + ); + } +} From 22b6e144a96407e1381a12c33d040cd2bf1f156a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=A0=EA=B4=80=EA=B7=9C?= Date: Sun, 17 Aug 2025 21:36:46 +0900 Subject: [PATCH 05/23] =?UTF-8?q?chore:=20=EC=BD=94=EB=93=9C=20=ED=8F=AC?= =?UTF-8?q?=EB=A9=A7=ED=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../in/koreatech/payment/acceptance/fixture/UserFixture.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/java/in/koreatech/payment/acceptance/fixture/UserFixture.java b/src/test/java/in/koreatech/payment/acceptance/fixture/UserFixture.java index 4525b7f..4d056d9 100644 --- a/src/test/java/in/koreatech/payment/acceptance/fixture/UserFixture.java +++ b/src/test/java/in/koreatech/payment/acceptance/fixture/UserFixture.java @@ -11,7 +11,6 @@ public class UserFixture { private final UserRepository userRepository; - private final JwtProvider jwtProvider; public UserFixture(UserRepository userRepository, JwtProvider jwtProvider) { From 2f39445d0c2db6be07b0ac07fbefca7ee96558c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=A0=EA=B4=80=EA=B7=9C?= Date: Wed, 20 Aug 2025 21:26:55 +0900 Subject: [PATCH 06/23] =?UTF-8?q?feat:=20CartMenuItemFixture=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/order/cart/model/CartMenuItem.java | 15 +++++++++++ .../fixture/CartMenuItemFixture.java | 25 +++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 src/test/java/in/koreatech/payment/acceptance/fixture/CartMenuItemFixture.java diff --git a/src/main/java/in/koreatech/koin/domain/order/cart/model/CartMenuItem.java b/src/main/java/in/koreatech/koin/domain/order/cart/model/CartMenuItem.java index 32028ac..0fcf47e 100644 --- a/src/main/java/in/koreatech/koin/domain/order/cart/model/CartMenuItem.java +++ b/src/main/java/in/koreatech/koin/domain/order/cart/model/CartMenuItem.java @@ -55,6 +55,21 @@ public class CartMenuItem extends BaseEntity { @OneToMany(mappedBy = "cartMenuItem", cascade = CascadeType.ALL, orphanRemoval = true) private List cartMenuItemOptions = new ArrayList<>(); + @Builder + private CartMenuItem( + Cart cart, + OrderableShopMenu orderableShopMenu, + OrderableShopMenuPrice orderableShopMenuPrice, + Integer quantity, + Boolean isModified + ) { + this.cart = cart; + this.orderableShopMenu = orderableShopMenu; + this.orderableShopMenuPrice = orderableShopMenuPrice; + this.quantity = quantity; + this.isModified = isModified; + } + public Integer calculateTotalAmount() { int totalOptionPrice = this.cartMenuItemOptions.stream() .mapToInt(CartMenuItemOption::getOptionPrice) diff --git a/src/test/java/in/koreatech/payment/acceptance/fixture/CartMenuItemFixture.java b/src/test/java/in/koreatech/payment/acceptance/fixture/CartMenuItemFixture.java new file mode 100644 index 0000000..e218df5 --- /dev/null +++ b/src/test/java/in/koreatech/payment/acceptance/fixture/CartMenuItemFixture.java @@ -0,0 +1,25 @@ +package in.koreatech.payment.acceptance.fixture; + +import org.springframework.stereotype.Component; + +import in.koreatech.koin.domain.order.cart.model.Cart; +import in.koreatech.koin.domain.order.cart.model.CartMenuItem; +import in.koreatech.koin.domain.order.shop.model.entity.menu.OrderableShopMenu; +import in.koreatech.koin.domain.order.shop.model.entity.menu.OrderableShopMenuPrice; + +@Component +@SuppressWarnings("NonAsciiCharacters") +public class CartMenuItemFixture { + + public static CartMenuItem 옵션이_없는_장바구니_메뉴( + Cart cart, OrderableShopMenu menu, OrderableShopMenuPrice menuPrice, Integer quantity + ) { + return CartMenuItem.builder() + .cart(cart) + .orderableShopMenu(menu) + .orderableShopMenuPrice(menuPrice) + .quantity(quantity) + .isModified(false) + .build(); + } +} From 31d2bfff6ff86dbdb66f6b3f0e3db842fb0ca769 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=A0=EA=B4=80=EA=B7=9C?= Date: Wed, 20 Aug 2025 21:36:24 +0900 Subject: [PATCH 07/23] =?UTF-8?q?feat:=20OrderableShopFixture=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../shop/model/entity/shop/OrderableShop.java | 19 +++++ .../shop/model/entity/shop/ShopOperation.java | 12 +++ .../koin/domain/owner/model/Owner.java | 78 +++++++++++++++++++ .../domain/owner/model/OwnerAttachment.java | 55 +++++++++++++ .../koin/domain/shop/model/shop/Shop.java | 52 ++++++++++++- .../fixture/CartMenuItemFixture.java | 2 +- .../fixture/OrderableShopFixture.java | 55 +++++++++++++ 7 files changed, 269 insertions(+), 4 deletions(-) create mode 100644 src/main/java/in/koreatech/koin/domain/owner/model/Owner.java create mode 100644 src/main/java/in/koreatech/koin/domain/owner/model/OwnerAttachment.java create mode 100644 src/test/java/in/koreatech/payment/acceptance/fixture/OrderableShopFixture.java diff --git a/src/main/java/in/koreatech/koin/domain/order/shop/model/entity/shop/OrderableShop.java b/src/main/java/in/koreatech/koin/domain/order/shop/model/entity/shop/OrderableShop.java index 5b2577f..39f55b0 100644 --- a/src/main/java/in/koreatech/koin/domain/order/shop/model/entity/shop/OrderableShop.java +++ b/src/main/java/in/koreatech/koin/domain/order/shop/model/entity/shop/OrderableShop.java @@ -64,4 +64,23 @@ public class OrderableShop extends BaseEntity { public Integer calculateDeliveryFee(Integer orderAmount) { return this.shop.getBaseDeliveryTips().calculateDeliveryTip(orderAmount); } + + @Builder + private OrderableShop( + Shop shop, + boolean delivery, + boolean takeout, + boolean serviceEvent, + Integer minimumOrderAmount, + boolean isDeleted, + List menuGroups + ) { + this.shop = shop; + this.delivery = delivery; + this.takeout = takeout; + this.serviceEvent = serviceEvent; + this.minimumOrderAmount = minimumOrderAmount; + this.isDeleted = isDeleted; + this.menuGroups = menuGroups; + } } diff --git a/src/main/java/in/koreatech/koin/domain/order/shop/model/entity/shop/ShopOperation.java b/src/main/java/in/koreatech/koin/domain/order/shop/model/entity/shop/ShopOperation.java index 7ccd4b6..c9f003e 100644 --- a/src/main/java/in/koreatech/koin/domain/order/shop/model/entity/shop/ShopOperation.java +++ b/src/main/java/in/koreatech/koin/domain/order/shop/model/entity/shop/ShopOperation.java @@ -14,6 +14,7 @@ import jakarta.persistence.JoinColumn; import jakarta.persistence.OneToOne; import jakarta.persistence.Table; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @@ -38,4 +39,15 @@ public class ShopOperation { @Column(name = "is_deleted", nullable = false) private boolean isDeleted = false; + + @Builder + private ShopOperation( + Shop shop, + boolean isOpen, + boolean isDeleted + ) { + this.shop = shop; + this.isOpen = isOpen; + this.isDeleted = isDeleted; + } } diff --git a/src/main/java/in/koreatech/koin/domain/owner/model/Owner.java b/src/main/java/in/koreatech/koin/domain/owner/model/Owner.java new file mode 100644 index 0000000..271f8a8 --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/owner/model/Owner.java @@ -0,0 +1,78 @@ +package in.koreatech.koin.domain.owner.model; + +import static jakarta.persistence.CascadeType.ALL; +import static jakarta.persistence.CascadeType.MERGE; +import static jakarta.persistence.CascadeType.PERSIST; +import static jakarta.persistence.CascadeType.REMOVE; +import static lombok.AccessLevel.PROTECTED; + +import java.util.ArrayList; +import java.util.List; + +import in.koreatech.koin.domain.user.model.User; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.MapsId; +import jakarta.persistence.OneToMany; +import jakarta.persistence.OneToOne; +import jakarta.persistence.Table; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Entity +@NoArgsConstructor(access = PROTECTED) +@Table(name = "owners") +public class Owner { + + @Id + @Column(name = "user_id", nullable = false) + private Integer id; + + @MapsId + @OneToOne(cascade = ALL) + @JoinColumn(name = "user_id", referencedColumnName = "id") + private User user; + + @NotNull + @Size(max = 12) + @Column(name = "company_registration_number", nullable = false, unique = true, length = 12) + private String companyRegistrationNumber; + + @Column(name = "grant_shop", columnDefinition = "TINYINT") + private boolean grantShop; + + @Column(name = "grant_event", columnDefinition = "TINYINT") + private boolean grantEvent; + + @NotNull + @Size(max = 11) + @Column(name = "account", nullable = false, unique = true, length = 11) + private String account; + + @OneToMany(cascade = {PERSIST, MERGE, REMOVE}, orphanRemoval = true) + @JoinColumn(name = "owner_id", updatable = false) + private List attachments = new ArrayList<>(); + + @Builder + private Owner( + User user, + String companyRegistrationNumber, + List attachments, + Boolean grantShop, + Boolean grantEvent, + String account + ) { + this.user = user; + this.companyRegistrationNumber = companyRegistrationNumber; + this.attachments = attachments; + this.grantShop = grantShop; + this.grantEvent = grantEvent; + this.account = account; + } +} diff --git a/src/main/java/in/koreatech/koin/domain/owner/model/OwnerAttachment.java b/src/main/java/in/koreatech/koin/domain/owner/model/OwnerAttachment.java new file mode 100644 index 0000000..2f17efc --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/owner/model/OwnerAttachment.java @@ -0,0 +1,55 @@ +package in.koreatech.koin.domain.owner.model; + +import static jakarta.persistence.CascadeType.*; +import static jakarta.persistence.GenerationType.IDENTITY; +import static lombok.AccessLevel.PROTECTED; + +import org.hibernate.annotations.Where; + +import in.koreatech.payment.common.model.BaseEntity; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import jakarta.validation.constraints.NotNull; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Entity +@Where(clause = "is_deleted=0") +@Table(name = "owner_attachments") +@NoArgsConstructor(access = PROTECTED) +public class OwnerAttachment extends BaseEntity { + + @Id + @GeneratedValue(strategy = IDENTITY) + private Integer id; + + @ManyToOne(cascade = {PERSIST, MERGE, REMOVE}) + @JoinColumn(name = "owner_id", nullable = false) + private Owner owner; + + @NotNull + @Column(name = "url", nullable = false) + private String url; + + @NotNull + @Column(name = "is_deleted", nullable = false) + private boolean isDeleted = false; + + @Builder + private OwnerAttachment( + String url, + Owner owner, + Boolean isDeleted + ) { + this.url = url; + this.owner = owner; + this.isDeleted = isDeleted; + } +} diff --git a/src/main/java/in/koreatech/koin/domain/shop/model/shop/Shop.java b/src/main/java/in/koreatech/koin/domain/shop/model/shop/Shop.java index 003c01a..ef0c22a 100644 --- a/src/main/java/in/koreatech/koin/domain/shop/model/shop/Shop.java +++ b/src/main/java/in/koreatech/koin/domain/shop/model/shop/Shop.java @@ -5,18 +5,16 @@ import static lombok.AccessLevel.PROTECTED; import java.util.ArrayList; -import java.util.Comparator; import java.util.HashSet; import java.util.List; -import java.util.Optional; import java.util.Set; import org.hibernate.annotations.Where; import in.koreatech.koin.domain.order.shop.model.domain.ShopBaseDeliveryTips; import in.koreatech.koin.domain.order.shop.model.domain.ShopMenuOrigins; -import in.koreatech.koin.domain.order.shop.model.entity.delivery.ShopBaseDeliveryTip; import in.koreatech.koin.domain.order.shop.model.entity.shop.ShopOperation; +import in.koreatech.koin.domain.owner.model.Owner; import in.koreatech.koin.domain.shop.model.event.EventArticle; import in.koreatech.koin.domain.shop.model.menu.Menu; import in.koreatech.koin.domain.shop.model.menu.MenuCategory; @@ -36,6 +34,7 @@ import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.PositiveOrZero; import jakarta.validation.constraints.Size; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @@ -50,6 +49,10 @@ public class Shop extends BaseEntity { @GeneratedValue(strategy = IDENTITY) private Integer id; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "owner_id", referencedColumnName = "user_id") + private Owner owner; + @Size(max = 50) @NotNull @Column(name = "name", nullable = false, length = 50) @@ -153,4 +156,47 @@ public class Shop extends BaseEntity { @Column(name = "notice", columnDefinition = "text") private String notice; + + @Builder + private Shop( + Owner owner, + String name, + String internalName, + String chosung, + String phone, + String address, + String description, + boolean delivery, + Integer deliveryPrice, + boolean payCard, + boolean payBank, + boolean isDeleted, + boolean isEvent, + String remarks, + Integer hit, + String bank, + String accountNumber, + ShopCategory shopMainCategory, + ShopOperation shopOperation + ) { + this.owner = owner; + this.name = name; + this.internalName = internalName; + this.chosung = chosung; + this.phone = phone; + this.address = address; + this.description = description; + this.delivery = delivery; + this.deliveryPrice = deliveryPrice; + this.payCard = payCard; + this.payBank = payBank; + this.isDeleted = isDeleted; + this.isEvent = isEvent; + this.remarks = remarks; + this.hit = hit; + this.bank = bank; + this.accountNumber = accountNumber; + this.shopMainCategory = shopMainCategory; + this.shopOperation = shopOperation; + } } diff --git a/src/test/java/in/koreatech/payment/acceptance/fixture/CartMenuItemFixture.java b/src/test/java/in/koreatech/payment/acceptance/fixture/CartMenuItemFixture.java index e218df5..238a4c6 100644 --- a/src/test/java/in/koreatech/payment/acceptance/fixture/CartMenuItemFixture.java +++ b/src/test/java/in/koreatech/payment/acceptance/fixture/CartMenuItemFixture.java @@ -11,7 +11,7 @@ @SuppressWarnings("NonAsciiCharacters") public class CartMenuItemFixture { - public static CartMenuItem 옵션이_없는_장바구니_메뉴( + public CartMenuItem 옵션이_없는_장바구니_메뉴( Cart cart, OrderableShopMenu menu, OrderableShopMenuPrice menuPrice, Integer quantity ) { return CartMenuItem.builder() diff --git a/src/test/java/in/koreatech/payment/acceptance/fixture/OrderableShopFixture.java b/src/test/java/in/koreatech/payment/acceptance/fixture/OrderableShopFixture.java new file mode 100644 index 0000000..b67d359 --- /dev/null +++ b/src/test/java/in/koreatech/payment/acceptance/fixture/OrderableShopFixture.java @@ -0,0 +1,55 @@ +package in.koreatech.payment.acceptance.fixture; + +import org.springframework.stereotype.Component; + +import in.koreatech.koin.domain.order.shop.model.entity.shop.OrderableShop; +import in.koreatech.koin.domain.order.shop.model.entity.shop.ShopOperation; +import in.koreatech.koin.domain.order.shop.repository.OrderableShopRepository; +import in.koreatech.koin.domain.shop.model.shop.Shop; + +@Component +@SuppressWarnings("NonAsciiCharacters") +public class OrderableShopFixture { + + private final OrderableShopRepository orderableShopRepository; + + public OrderableShopFixture(OrderableShopRepository orderableShopRepository) { + this.orderableShopRepository = orderableShopRepository; + } + + public OrderableShop 김밥천국() { + ShopOperation shopOperation = ShopOperation.builder() + .isOpen(true) + .isDeleted(false) + .build(); + + Shop shop = Shop.builder() + .name("김밥천국") + .internalName("김천") + .chosung("김") + .phone("010-7574-1212") + .address("천안시 동남구 병천면 1600") + .description("김밥천국입니다.") + .delivery(true) + .deliveryPrice(3000) + .payCard(true) + .payBank(true) + .isDeleted(false) + .isEvent(false) + .remarks("비고") + .hit(0) + .bank("국민") + .accountNumber("01022595923") + .shopOperation(shopOperation) + .build(); + + return orderableShopRepository.save(OrderableShop.builder() + .shop(shop) + .minimumOrderAmount(15000) + .takeout(true) + .delivery(true) + .serviceEvent(false) + .isDeleted(false) + .build()); + } +} From 4ac5794580cf69a5f6c9c24ffc106df29b1acda5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=A0=EA=B4=80=EA=B7=9C?= Date: Wed, 20 Aug 2025 21:44:55 +0900 Subject: [PATCH 08/23] =?UTF-8?q?feat:=20OrderableShopMenuFixture=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../model/entity/menu/OrderableShopMenu.java | 21 +++++++++++++++++++ .../fixture/OrderableShopMenuFixture.java | 21 +++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 src/test/java/in/koreatech/payment/acceptance/fixture/OrderableShopMenuFixture.java diff --git a/src/main/java/in/koreatech/koin/domain/order/shop/model/entity/menu/OrderableShopMenu.java b/src/main/java/in/koreatech/koin/domain/order/shop/model/entity/menu/OrderableShopMenu.java index 8cb877f..29cc205 100644 --- a/src/main/java/in/koreatech/koin/domain/order/shop/model/entity/menu/OrderableShopMenu.java +++ b/src/main/java/in/koreatech/koin/domain/order/shop/model/entity/menu/OrderableShopMenu.java @@ -64,4 +64,25 @@ public class OrderableShopMenu extends BaseEntity { @OneToMany(mappedBy = "menu", cascade = CascadeType.ALL, orphanRemoval = true) private List menuImages = new ArrayList<>(); + + @Builder + private OrderableShopMenu( + OrderableShop orderableShop, + String name, + String description, + Boolean isSoldOut, + Boolean isDeleted, + List menuOptionGroupMap, + List menuPrices, + List menuImages + ) { + this.orderableShop = orderableShop; + this.name = name; + this.description = description; + this.isSoldOut = isSoldOut; + this.isDeleted = isDeleted; + this.menuOptionGroupMap = menuOptionGroupMap; + this.menuPrices = menuPrices; + this.menuImages = menuImages; + } } diff --git a/src/test/java/in/koreatech/payment/acceptance/fixture/OrderableShopMenuFixture.java b/src/test/java/in/koreatech/payment/acceptance/fixture/OrderableShopMenuFixture.java new file mode 100644 index 0000000..b3da65b --- /dev/null +++ b/src/test/java/in/koreatech/payment/acceptance/fixture/OrderableShopMenuFixture.java @@ -0,0 +1,21 @@ +package in.koreatech.payment.acceptance.fixture; + +import org.springframework.stereotype.Component; + +import in.koreatech.koin.domain.order.shop.model.entity.menu.OrderableShopMenu; +import in.koreatech.koin.domain.order.shop.model.entity.shop.OrderableShop; + +@Component +@SuppressWarnings("NonAsciiCharacters") +public class OrderableShopMenuFixture { + + public OrderableShopMenu 주문_가능_상점_메뉴(OrderableShop shop, String name) { + return OrderableShopMenu.builder() + .orderableShop(shop) + .name(name) + .description("메뉴") + .isSoldOut(false) + .isDeleted(false) + .build(); + } +} From 9686f1a989ee6d24a5d556a1b7b3a5d277392b53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=A0=EA=B4=80=EA=B7=9C?= Date: Wed, 20 Aug 2025 21:46:39 +0900 Subject: [PATCH 09/23] =?UTF-8?q?feat:=20OrderShopMenuPriceFixture=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../entity/menu/OrderableShopMenuPrice.java | 14 +++++++++++++ .../fixture/OrderShopMenuPriceFixture.java | 20 +++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 src/test/java/in/koreatech/payment/acceptance/fixture/OrderShopMenuPriceFixture.java diff --git a/src/main/java/in/koreatech/koin/domain/order/shop/model/entity/menu/OrderableShopMenuPrice.java b/src/main/java/in/koreatech/koin/domain/order/shop/model/entity/menu/OrderableShopMenuPrice.java index 6d6de43..8ad62d6 100644 --- a/src/main/java/in/koreatech/koin/domain/order/shop/model/entity/menu/OrderableShopMenuPrice.java +++ b/src/main/java/in/koreatech/koin/domain/order/shop/model/entity/menu/OrderableShopMenuPrice.java @@ -14,6 +14,7 @@ import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @@ -41,4 +42,17 @@ public class OrderableShopMenuPrice extends BaseEntity { @Column(name = "is_deleted", nullable = false) private Boolean isDeleted = false; + + @Builder + private OrderableShopMenuPrice( + OrderableShopMenu menu, + String name, + Integer price, + Boolean isDeleted + ) { + this.menu = menu; + this.name = name; + this.price = price; + this.isDeleted = isDeleted; + } } diff --git a/src/test/java/in/koreatech/payment/acceptance/fixture/OrderShopMenuPriceFixture.java b/src/test/java/in/koreatech/payment/acceptance/fixture/OrderShopMenuPriceFixture.java new file mode 100644 index 0000000..dd64bd8 --- /dev/null +++ b/src/test/java/in/koreatech/payment/acceptance/fixture/OrderShopMenuPriceFixture.java @@ -0,0 +1,20 @@ +package in.koreatech.payment.acceptance.fixture; + +import org.springframework.stereotype.Component; + +import in.koreatech.koin.domain.order.shop.model.entity.menu.OrderableShopMenu; +import in.koreatech.koin.domain.order.shop.model.entity.menu.OrderableShopMenuPrice; + +@Component +@SuppressWarnings("NonAsciiCharacters") +public class OrderShopMenuPriceFixture { + + public OrderableShopMenuPrice 주문_가능_상점_메뉴_가격(OrderableShopMenu menu, String name, Integer price) { + return OrderableShopMenuPrice.builder() + .price(price) + .name(name) + .menu(menu) + .isDeleted(false) + .build(); + } +} From 1ee72bf272b51d7889ec02c1d18eaa5a4334226f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=A0=EA=B4=80=EA=B7=9C?= Date: Wed, 20 Aug 2025 21:56:09 +0900 Subject: [PATCH 10/23] =?UTF-8?q?feat:=20=EC=A3=BC=EB=AC=B8=EA=B0=80?= =?UTF-8?q?=EB=8A=A5=EC=83=81=EC=A0=90=EB=A9=94=EB=89=B4,=20=EC=A3=BC?= =?UTF-8?q?=EB=AC=B8=EA=B0=80=EB=8A=A5=EC=83=81=EC=A0=90=EB=A9=94=EB=89=B4?= =?UTF-8?q?=EA=B0=80=EA=B2=A9=20=EB=A6=AC=ED=8C=8C=EC=A7=80=ED=86=A0?= =?UTF-8?q?=EB=A6=AC=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../menu/OrderableShopMenuPriceRepository.java | 9 +++++++++ .../menu/OrderableShopMenuRepository.java | 8 ++++++++ .../fixture/OrderableShopMenuFixture.java | 11 +++++++++-- ...ture.java => OrderableShopMenuPriceFixture.java} | 13 ++++++++++--- 4 files changed, 36 insertions(+), 5 deletions(-) create mode 100644 src/main/java/in/koreatech/koin/domain/order/shop/repository/menu/OrderableShopMenuPriceRepository.java create mode 100644 src/main/java/in/koreatech/koin/domain/order/shop/repository/menu/OrderableShopMenuRepository.java rename src/test/java/in/koreatech/payment/acceptance/fixture/{OrderShopMenuPriceFixture.java => OrderableShopMenuPriceFixture.java} (50%) diff --git a/src/main/java/in/koreatech/koin/domain/order/shop/repository/menu/OrderableShopMenuPriceRepository.java b/src/main/java/in/koreatech/koin/domain/order/shop/repository/menu/OrderableShopMenuPriceRepository.java new file mode 100644 index 0000000..370ed06 --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/order/shop/repository/menu/OrderableShopMenuPriceRepository.java @@ -0,0 +1,9 @@ +package in.koreatech.koin.domain.order.shop.repository.menu; + +import org.springframework.data.jpa.repository.JpaRepository; + +import in.koreatech.koin.domain.order.shop.model.entity.menu.OrderableShopMenuPrice; + +public interface OrderableShopMenuPriceRepository extends JpaRepository { + +} diff --git a/src/main/java/in/koreatech/koin/domain/order/shop/repository/menu/OrderableShopMenuRepository.java b/src/main/java/in/koreatech/koin/domain/order/shop/repository/menu/OrderableShopMenuRepository.java new file mode 100644 index 0000000..a183bce --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/order/shop/repository/menu/OrderableShopMenuRepository.java @@ -0,0 +1,8 @@ +package in.koreatech.koin.domain.order.shop.repository.menu; + +import org.springframework.data.jpa.repository.JpaRepository; + +import in.koreatech.koin.domain.order.shop.model.entity.menu.OrderableShopMenu; + +public interface OrderableShopMenuRepository extends JpaRepository { +} diff --git a/src/test/java/in/koreatech/payment/acceptance/fixture/OrderableShopMenuFixture.java b/src/test/java/in/koreatech/payment/acceptance/fixture/OrderableShopMenuFixture.java index b3da65b..f6d16a2 100644 --- a/src/test/java/in/koreatech/payment/acceptance/fixture/OrderableShopMenuFixture.java +++ b/src/test/java/in/koreatech/payment/acceptance/fixture/OrderableShopMenuFixture.java @@ -4,18 +4,25 @@ import in.koreatech.koin.domain.order.shop.model.entity.menu.OrderableShopMenu; import in.koreatech.koin.domain.order.shop.model.entity.shop.OrderableShop; +import in.koreatech.koin.domain.order.shop.repository.menu.OrderableShopMenuRepository; @Component @SuppressWarnings("NonAsciiCharacters") public class OrderableShopMenuFixture { + private final OrderableShopMenuRepository orderableShopMenuRepository; + + public OrderableShopMenuFixture(OrderableShopMenuRepository orderableShopMenuRepository) { + this.orderableShopMenuRepository = orderableShopMenuRepository; + } + public OrderableShopMenu 주문_가능_상점_메뉴(OrderableShop shop, String name) { - return OrderableShopMenu.builder() + return orderableShopMenuRepository.save(OrderableShopMenu.builder() .orderableShop(shop) .name(name) .description("메뉴") .isSoldOut(false) .isDeleted(false) - .build(); + .build()); } } diff --git a/src/test/java/in/koreatech/payment/acceptance/fixture/OrderShopMenuPriceFixture.java b/src/test/java/in/koreatech/payment/acceptance/fixture/OrderableShopMenuPriceFixture.java similarity index 50% rename from src/test/java/in/koreatech/payment/acceptance/fixture/OrderShopMenuPriceFixture.java rename to src/test/java/in/koreatech/payment/acceptance/fixture/OrderableShopMenuPriceFixture.java index dd64bd8..33ff4a7 100644 --- a/src/test/java/in/koreatech/payment/acceptance/fixture/OrderShopMenuPriceFixture.java +++ b/src/test/java/in/koreatech/payment/acceptance/fixture/OrderableShopMenuPriceFixture.java @@ -4,17 +4,24 @@ import in.koreatech.koin.domain.order.shop.model.entity.menu.OrderableShopMenu; import in.koreatech.koin.domain.order.shop.model.entity.menu.OrderableShopMenuPrice; +import in.koreatech.koin.domain.order.shop.repository.menu.OrderableShopMenuPriceRepository; @Component @SuppressWarnings("NonAsciiCharacters") -public class OrderShopMenuPriceFixture { +public class OrderableShopMenuPriceFixture { + + private final OrderableShopMenuPriceRepository orderableShopMenuPriceRepository; + + public OrderableShopMenuPriceFixture(OrderableShopMenuPriceRepository orderableShopMenuPriceRepository) { + this.orderableShopMenuPriceRepository = orderableShopMenuPriceRepository; + } public OrderableShopMenuPrice 주문_가능_상점_메뉴_가격(OrderableShopMenu menu, String name, Integer price) { - return OrderableShopMenuPrice.builder() + return orderableShopMenuPriceRepository.save(OrderableShopMenuPrice.builder() .price(price) .name(name) .menu(menu) .isDeleted(false) - .build(); + .build()); } } From 6e77dabcf6d662ad988cb57f57d535aaa327b7f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=A0=EA=B4=80=EA=B7=9C?= Date: Wed, 20 Aug 2025 22:10:04 +0900 Subject: [PATCH 11/23] =?UTF-8?q?feat:=20=EC=A3=BC=EB=AC=B8=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=EC=83=9D=EC=84=B1=20Fake=20=EA=B0=9D=EC=B2=B4=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../payment/acceptance/util/FakeOrderIdGenerator.java | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 src/test/java/in/koreatech/payment/acceptance/util/FakeOrderIdGenerator.java diff --git a/src/test/java/in/koreatech/payment/acceptance/util/FakeOrderIdGenerator.java b/src/test/java/in/koreatech/payment/acceptance/util/FakeOrderIdGenerator.java new file mode 100644 index 0000000..7754f9a --- /dev/null +++ b/src/test/java/in/koreatech/payment/acceptance/util/FakeOrderIdGenerator.java @@ -0,0 +1,10 @@ +package in.koreatech.payment.acceptance.util; + +import in.koreatech.payment.util.OrderIdGenerator; + +public class FakeOrderIdGenerator implements OrderIdGenerator { + @Override + public String generateOrderId() { + return "a4CWyWY5m89PNh7xJwhk1"; + } +} From 420bb292cfc4945c3bb4db7457d44f894f2ede40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=A0=EA=B4=80=EA=B7=9C?= Date: Wed, 20 Aug 2025 22:19:01 +0900 Subject: [PATCH 12/23] =?UTF-8?q?feat:=20=EC=A3=BC=EB=AC=B8=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=EC=83=9D=EC=84=B1=20Fake=20=EA=B0=9D=EC=B2=B4=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../payment/acceptance/util/FakeOrderIdGenerator.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/test/java/in/koreatech/payment/acceptance/util/FakeOrderIdGenerator.java b/src/test/java/in/koreatech/payment/acceptance/util/FakeOrderIdGenerator.java index 7754f9a..79fb874 100644 --- a/src/test/java/in/koreatech/payment/acceptance/util/FakeOrderIdGenerator.java +++ b/src/test/java/in/koreatech/payment/acceptance/util/FakeOrderIdGenerator.java @@ -1,10 +1,15 @@ package in.koreatech.payment.acceptance.util; +import org.springframework.context.annotation.Primary; +import org.springframework.stereotype.Component; + import in.koreatech.payment.util.OrderIdGenerator; -public class FakeOrderIdGenerator implements OrderIdGenerator { +@Component +@Primary +class FakeOrderIdGenerator implements OrderIdGenerator { @Override public String generateOrderId() { - return "a4CWyWY5m89PNh7xJwhk1"; + return "FAKE_ORDER_123"; } } From 67b15724044f44881ad46c5b286bab9641d25f90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=A0=EA=B4=80=EA=B7=9C?= Date: Wed, 20 Aug 2025 22:23:22 +0900 Subject: [PATCH 13/23] =?UTF-8?q?feat:=20=EC=9E=A5=EB=B0=94=EA=B5=AC?= =?UTF-8?q?=EB=8B=88=20=EC=97=B0=EA=B0=84=EA=B4=80=EA=B3=84=20=EB=A9=94?= =?UTF-8?q?=EC=86=8C=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../koin/domain/order/cart/model/Cart.java | 42 ++++++++++++++++--- .../domain/order/cart/model/CartMenuItem.java | 30 +++++++++++++ 2 files changed, 67 insertions(+), 5 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/order/cart/model/Cart.java b/src/main/java/in/koreatech/koin/domain/order/cart/model/Cart.java index 5b134c8..b57f82e 100644 --- a/src/main/java/in/koreatech/koin/domain/order/cart/model/Cart.java +++ b/src/main/java/in/koreatech/koin/domain/order/cart/model/Cart.java @@ -7,7 +7,9 @@ import java.util.List; import java.util.Optional; -import in.koreatech.koin.domain.order.cart.exception.CartAccessDeniedException; +import in.koreatech.koin.domain.order.shop.model.entity.menu.OrderableShopMenu; +import in.koreatech.koin.domain.order.shop.model.entity.menu.OrderableShopMenuOption; +import in.koreatech.koin.domain.order.shop.model.entity.menu.OrderableShopMenuPrice; import in.koreatech.koin.domain.order.shop.model.entity.shop.OrderableShop; import in.koreatech.koin.domain.user.model.User; import in.koreatech.payment.common.model.BaseEntity; @@ -48,15 +50,45 @@ public class Cart extends BaseEntity { @OneToMany(mappedBy = "cart", cascade = CascadeType.ALL, orphanRemoval = true) List cartMenuItems = new ArrayList<>(); + @Builder + private Cart( + User user, + OrderableShop orderableShop + ) { + this.user = user; + this.orderableShop = orderableShop; + } + public Integer calculateItemsAmount() { return this.cartMenuItems.stream() .mapToInt(CartMenuItem::calculateTotalAmount) .sum(); } - @Builder - public Cart(User user, OrderableShop orderableShop) { - this.user = user; - this.orderableShop = orderableShop; + // TODO. 코인 서버와 동일 로직으로 변경 + public void addItem(OrderableShopMenu menu, OrderableShopMenuPrice price, List options, + Integer quantity) { + // 장바구니에 옵션 까지 전부 동일한 메뉴가 이미 존재 하는 경우, 담긴 상품 수량 증가 + Optional existingItem = findSameItem(menu, price, options); + + if (existingItem.isPresent()) { + existingItem.get().increaseQuantity(quantity); + } else { + this.cartMenuItems.add(CartMenuItem.builder() + .cart(this) + .orderableShopMenu(menu) + .orderableShopMenuPrice(price) + .quantity(quantity) + .isModified(false) + .build() + ); + } + } + + private Optional findSameItem(OrderableShopMenu menu, OrderableShopMenuPrice price, + List options) { + return this.cartMenuItems.stream() + .filter(item -> item.isSameItem(menu, price, options)) + .findFirst(); } } diff --git a/src/main/java/in/koreatech/koin/domain/order/cart/model/CartMenuItem.java b/src/main/java/in/koreatech/koin/domain/order/cart/model/CartMenuItem.java index 0fcf47e..3d50ec9 100644 --- a/src/main/java/in/koreatech/koin/domain/order/cart/model/CartMenuItem.java +++ b/src/main/java/in/koreatech/koin/domain/order/cart/model/CartMenuItem.java @@ -7,6 +7,7 @@ import java.util.List; import in.koreatech.koin.domain.order.shop.model.entity.menu.OrderableShopMenu; +import in.koreatech.koin.domain.order.shop.model.entity.menu.OrderableShopMenuOption; import in.koreatech.koin.domain.order.shop.model.entity.menu.OrderableShopMenuPrice; import in.koreatech.payment.common.model.BaseEntity; import jakarta.persistence.CascadeType; @@ -77,4 +78,33 @@ public Integer calculateTotalAmount() { return (this.orderableShopMenuPrice.getPrice() + totalOptionPrice) * this.quantity; } + + public void increaseQuantity(Integer amount) { + this.quantity += amount; + } + + public boolean isSameItem(OrderableShopMenu menu, OrderableShopMenuPrice price, List options) { + // 메뉴와 가격 ID가 다른 경우 + if (!this.orderableShopMenu.getId().equals(menu.getId()) || !this.orderableShopMenuPrice.getId().equals(price.getId())) { + return false; + } + + // 선택한 옵션의 개수가 다른 경우 + if (this.cartMenuItemOptions.size() != options.size()) { + return false; + } + + // 선택한 옵션의 구성이 다른 경우 (정렬 후 비교) + List existingOptionIds = this.cartMenuItemOptions.stream() + .map(opt -> opt.getOrderableShopMenuOption().getId()) + .sorted() + .toList(); + + List newOptionIds = options.stream() + .map(OrderableShopMenuOption::getId) + .sorted() + .toList(); + + return existingOptionIds.equals(newOptionIds); + } } From 1e5e3702dbebbfbb75b6605335899f2d8edfe3f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=A0=EA=B4=80=EA=B7=9C?= Date: Wed, 20 Aug 2025 23:02:31 +0900 Subject: [PATCH 14/23] =?UTF-8?q?test:=20=EC=9E=84=EC=8B=9C=20=EB=B0=B0?= =?UTF-8?q?=EB=8B=AC=20=EA=B2=B0=EC=A0=9C=20=EC=A0=95=EB=B3=B4=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=20API=20=ED=86=B5=ED=95=A9=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../koin/domain/shop/model/shop/Shop.java | 4 + .../shop/repository/ShopRepository.java | 10 ++ .../koin/domain/user/model/User.java | 3 - .../payment/service/TossService.java | 14 +-- .../payment/acceptance/AcceptanceTest.java | 2 + .../acceptance/domain/PaymentApiTest.java | 103 ++++++++++++++++++ .../acceptance/fixture/CartFixture.java | 16 +++ .../fixture/OrderableShopFixture.java | 28 +---- .../acceptance/fixture/ShopFixture.java | 48 ++++++++ 9 files changed, 191 insertions(+), 37 deletions(-) create mode 100644 src/main/java/in/koreatech/koin/domain/shop/repository/ShopRepository.java create mode 100644 src/test/java/in/koreatech/payment/acceptance/domain/PaymentApiTest.java create mode 100644 src/test/java/in/koreatech/payment/acceptance/fixture/ShopFixture.java diff --git a/src/main/java/in/koreatech/koin/domain/shop/model/shop/Shop.java b/src/main/java/in/koreatech/koin/domain/shop/model/shop/Shop.java index ef0c22a..5db991d 100644 --- a/src/main/java/in/koreatech/koin/domain/shop/model/shop/Shop.java +++ b/src/main/java/in/koreatech/koin/domain/shop/model/shop/Shop.java @@ -199,4 +199,8 @@ private Shop( this.shopMainCategory = shopMainCategory; this.shopOperation = shopOperation; } + + public void setShopOperation(ShopOperation shopOperation) { + this.shopOperation = shopOperation; + } } diff --git a/src/main/java/in/koreatech/koin/domain/shop/repository/ShopRepository.java b/src/main/java/in/koreatech/koin/domain/shop/repository/ShopRepository.java new file mode 100644 index 0000000..81f17ba --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/shop/repository/ShopRepository.java @@ -0,0 +1,10 @@ +package in.koreatech.koin.domain.shop.repository; + +import org.springframework.data.repository.Repository; + +import in.koreatech.koin.domain.shop.model.shop.Shop; + +public interface ShopRepository extends Repository { + + Shop save(Shop shop); +} diff --git a/src/main/java/in/koreatech/koin/domain/user/model/User.java b/src/main/java/in/koreatech/koin/domain/user/model/User.java index 3abe225..f73b955 100644 --- a/src/main/java/in/koreatech/koin/domain/user/model/User.java +++ b/src/main/java/in/koreatech/koin/domain/user/model/User.java @@ -3,8 +3,6 @@ import static jakarta.persistence.GenerationType.IDENTITY; import static lombok.AccessLevel.PROTECTED; -import org.hibernate.annotations.Where; - import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.Id; @@ -16,7 +14,6 @@ @Getter @Entity @Table(schema = "koin", name = "users") -@Where(clause = "is_deleted=0") @NoArgsConstructor(access = PROTECTED) public class User { diff --git a/src/main/java/in/koreatech/payment/service/TossService.java b/src/main/java/in/koreatech/payment/service/TossService.java index 32caba8..2cec10d 100644 --- a/src/main/java/in/koreatech/payment/service/TossService.java +++ b/src/main/java/in/koreatech/payment/service/TossService.java @@ -27,7 +27,7 @@ import in.koreatech.payment.client.TossPaymentClient; import in.koreatech.payment.client.dto.response.PaymentCancelResponse; import in.koreatech.payment.client.dto.response.TossPaymentConfirmResponse; -import in.koreatech.payment.common.auth.JwtTokenResolver; +import in.koreatech.payment.common.auth.JwtProvider; import in.koreatech.payment.dto.request.TemporaryDeliveryPaymentSaveRequest; import in.koreatech.payment.dto.request.TemporaryTakeoutPaymentSaveRequest; import in.koreatech.payment.dto.response.PaymentConfirmResponse; @@ -50,7 +50,7 @@ public class TossService implements PaymentService { private final OrderIdGenerator orderIdGenerator; - private final JwtTokenResolver jwtTokenResolver; + private final JwtProvider jwtProvider; private final UserRepository userRepository; private final TossPaymentClient tossPaymentClient; private final PaymentRepository paymentRepository; @@ -65,7 +65,7 @@ public class TossService implements PaymentService { @Transactional public String createTemporaryDeliveryPayment(String accessToken, TemporaryDeliveryPaymentSaveRequest request) { - Integer userId = jwtTokenResolver.getUserId(accessToken); + Integer userId = jwtProvider.getUserId(accessToken); User user = userRepository.getById(userId); Cart cart = cartRepository.getCartByUserId(user.getId()); @@ -108,7 +108,7 @@ public String createTemporaryDeliveryPayment(String accessToken, TemporaryDelive @Transactional public String createTemporaryTakeoutPayment(String accessToken, TemporaryTakeoutPaymentSaveRequest request) { - Integer userId = jwtTokenResolver.getUserId(accessToken); + Integer userId = jwtProvider.getUserId(accessToken); User user = userRepository.getById(userId); Cart cart = cartRepository.getCartByUserId(user.getId()); @@ -146,7 +146,7 @@ public String createTemporaryTakeoutPayment(String accessToken, TemporaryTakeout @Transactional public PaymentConfirmResponse confirmPayment(String accessToken, String paymentKey, String orderId, Integer amount) { - Integer userId = jwtTokenResolver.getUserId(accessToken); + Integer userId = jwtProvider.getUserId(accessToken); User user = userRepository.getById(userId); TemporaryPayment temporaryPayment = temporaryPaymentRedisRepository.getById(orderId); temporaryPayment.validateMatches(orderId, user.getId(), amount); @@ -178,7 +178,7 @@ public PaymentConfirmResponse confirmPayment(String accessToken, String paymentK @Transactional public List cancelPayment(String accessToken, Integer paymentId, String cancelReason) { - Integer userId = jwtTokenResolver.getUserId(accessToken); + Integer userId = jwtProvider.getUserId(accessToken); User user = userRepository.getById(userId); Payment payment = paymentRepository.getById(paymentId); if (payment.getPaymentStatus().isCanceled()) { @@ -214,7 +214,7 @@ public List cancelPayment(String accessToken, Integer paymentId, } public PaymentResponse getPayment(String accessToken, Integer paymentId) { - Integer userId = jwtTokenResolver.getUserId(accessToken); + Integer userId = jwtProvider.getUserId(accessToken); User user = userRepository.getById(userId); Payment payment = paymentRepository.getById(paymentId); payment.validateUserIdMatches(user.getId()); diff --git a/src/test/java/in/koreatech/payment/acceptance/AcceptanceTest.java b/src/test/java/in/koreatech/payment/acceptance/AcceptanceTest.java index d312399..0c0af96 100644 --- a/src/test/java/in/koreatech/payment/acceptance/AcceptanceTest.java +++ b/src/test/java/in/koreatech/payment/acceptance/AcceptanceTest.java @@ -7,6 +7,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Import; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.DynamicPropertyRegistry; import org.springframework.test.context.DynamicPropertySource; @@ -21,6 +22,7 @@ @SpringBootTest @AutoConfigureMockMvc +@Import(DBInitializer.class) @ActiveProfiles("test") @Transactional @TestInstance(value = PER_CLASS) diff --git a/src/test/java/in/koreatech/payment/acceptance/domain/PaymentApiTest.java b/src/test/java/in/koreatech/payment/acceptance/domain/PaymentApiTest.java new file mode 100644 index 0000000..c7131ba --- /dev/null +++ b/src/test/java/in/koreatech/payment/acceptance/domain/PaymentApiTest.java @@ -0,0 +1,103 @@ +package in.koreatech.payment.acceptance.domain; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.util.List; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; + +import in.koreatech.koin.domain.order.cart.model.Cart; +import in.koreatech.koin.domain.order.shop.model.entity.menu.OrderableShopMenu; +import in.koreatech.koin.domain.order.shop.model.entity.menu.OrderableShopMenuPrice; +import in.koreatech.koin.domain.order.shop.model.entity.shop.OrderableShop; +import in.koreatech.koin.domain.shop.model.shop.Shop; +import in.koreatech.koin.domain.user.model.User; +import in.koreatech.payment.acceptance.AcceptanceTest; +import in.koreatech.payment.acceptance.fixture.CartFixture; +import in.koreatech.payment.acceptance.fixture.OrderableShopFixture; +import in.koreatech.payment.acceptance.fixture.OrderableShopMenuFixture; +import in.koreatech.payment.acceptance.fixture.OrderableShopMenuPriceFixture; +import in.koreatech.payment.acceptance.fixture.ShopFixture; +import in.koreatech.payment.acceptance.fixture.UserFixture; +import in.koreatech.payment.common.auth.JwtProvider; + +public class PaymentApiTest extends AcceptanceTest { + + @Autowired + private UserFixture userFixture; + + @Autowired + private JwtProvider jwtProvider; + + @Autowired + private CartFixture cartFixture; + + @Autowired + private ShopFixture shopFixture; + + @Autowired + private OrderableShopFixture orderableShopFixture; + + @Autowired + private OrderableShopMenuFixture orderableShopMenuFixture; + + @Autowired + private OrderableShopMenuPriceFixture orderableShopMenuPriceFixture; + + private User user; + private String token; + private Shop shop; + private OrderableShop orderableShop; + private Cart cart; + private OrderableShopMenu menuGimbap; + private OrderableShopMenuPrice gimbapPrice; + + @BeforeAll + void setUp() { + user = userFixture.코인_유저(); + token = jwtProvider.createToken(user); + shop = shopFixture.김밥천국(); + orderableShop = orderableShopFixture.주문_가능_김밥천국(shop); + cart = cartFixture.장바구니(user, orderableShop); + menuGimbap = orderableShopMenuFixture.주문_가능_상점_메뉴(orderableShop, "김밥"); + gimbapPrice = orderableShopMenuPriceFixture.주문_가능_상점_메뉴_가격(menuGimbap, "소고기 김밥", 6000); + cartFixture.addOrderMenuItem(cart, menuGimbap, gimbapPrice, List.of(), 4); + } + + @Nested + class TemporaryPaymentSuccess { + + @Test + void 임시_배달_결제_정보_저장에_성공한다() throws Exception { + mockMvc.perform( + post("/payments/delivery/temporary") + .header("Authorization", "Bearer " + token) + .contentType(MediaType.APPLICATION_JSON) + .content(""" + { + "address": "충청남도 천안시 동남구 병천면 충절로 1600 은솔관 422호", + "phone_number": "01012345678", + "to_owner": "리뷰 이벤트 감사합니다.", + "to_rider": "문 앞에 놔주세요.", + "total_menu_price": 24000, + "delivery_tip": 0, + "provide_cutlery": true, + "total_amount": 24000 + } + """) + ) + .andExpect(status().isOk()) + .andExpect(content().json(""" + { + "order_id": "FAKE_ORDER_123" + } + """)); + } + } +} diff --git a/src/test/java/in/koreatech/payment/acceptance/fixture/CartFixture.java b/src/test/java/in/koreatech/payment/acceptance/fixture/CartFixture.java index c729854..17a3d3d 100644 --- a/src/test/java/in/koreatech/payment/acceptance/fixture/CartFixture.java +++ b/src/test/java/in/koreatech/payment/acceptance/fixture/CartFixture.java @@ -1,9 +1,14 @@ package in.koreatech.payment.acceptance.fixture; +import java.util.List; + import org.springframework.stereotype.Component; import in.koreatech.koin.domain.order.cart.model.Cart; import in.koreatech.koin.domain.order.cart.repository.CartRepository; +import in.koreatech.koin.domain.order.shop.model.entity.menu.OrderableShopMenu; +import in.koreatech.koin.domain.order.shop.model.entity.menu.OrderableShopMenuOption; +import in.koreatech.koin.domain.order.shop.model.entity.menu.OrderableShopMenuPrice; import in.koreatech.koin.domain.order.shop.model.entity.shop.OrderableShop; import in.koreatech.koin.domain.user.model.User; @@ -24,4 +29,15 @@ public CartFixture(CartRepository cartRepository) { .build() ); } + + public void addOrderMenuItem( + Cart cart, + OrderableShopMenu menu, + OrderableShopMenuPrice price, + List options, + Integer quantity + ) { + cart.addItem(menu, price, options, quantity); + cartRepository.save(cart); + } } diff --git a/src/test/java/in/koreatech/payment/acceptance/fixture/OrderableShopFixture.java b/src/test/java/in/koreatech/payment/acceptance/fixture/OrderableShopFixture.java index b67d359..eae0833 100644 --- a/src/test/java/in/koreatech/payment/acceptance/fixture/OrderableShopFixture.java +++ b/src/test/java/in/koreatech/payment/acceptance/fixture/OrderableShopFixture.java @@ -3,7 +3,6 @@ import org.springframework.stereotype.Component; import in.koreatech.koin.domain.order.shop.model.entity.shop.OrderableShop; -import in.koreatech.koin.domain.order.shop.model.entity.shop.ShopOperation; import in.koreatech.koin.domain.order.shop.repository.OrderableShopRepository; import in.koreatech.koin.domain.shop.model.shop.Shop; @@ -17,32 +16,7 @@ public OrderableShopFixture(OrderableShopRepository orderableShopRepository) { this.orderableShopRepository = orderableShopRepository; } - public OrderableShop 김밥천국() { - ShopOperation shopOperation = ShopOperation.builder() - .isOpen(true) - .isDeleted(false) - .build(); - - Shop shop = Shop.builder() - .name("김밥천국") - .internalName("김천") - .chosung("김") - .phone("010-7574-1212") - .address("천안시 동남구 병천면 1600") - .description("김밥천국입니다.") - .delivery(true) - .deliveryPrice(3000) - .payCard(true) - .payBank(true) - .isDeleted(false) - .isEvent(false) - .remarks("비고") - .hit(0) - .bank("국민") - .accountNumber("01022595923") - .shopOperation(shopOperation) - .build(); - + public OrderableShop 주문_가능_김밥천국(Shop shop) { return orderableShopRepository.save(OrderableShop.builder() .shop(shop) .minimumOrderAmount(15000) diff --git a/src/test/java/in/koreatech/payment/acceptance/fixture/ShopFixture.java b/src/test/java/in/koreatech/payment/acceptance/fixture/ShopFixture.java new file mode 100644 index 0000000..476c082 --- /dev/null +++ b/src/test/java/in/koreatech/payment/acceptance/fixture/ShopFixture.java @@ -0,0 +1,48 @@ +package in.koreatech.payment.acceptance.fixture; + +import org.springframework.stereotype.Component; + +import in.koreatech.koin.domain.order.shop.model.entity.shop.ShopOperation; +import in.koreatech.koin.domain.shop.model.shop.Shop; +import in.koreatech.koin.domain.shop.repository.ShopRepository; + +@Component +@SuppressWarnings("NonAsciiCharacters") +public class ShopFixture { + + private final ShopRepository shopRepository; + + public ShopFixture(ShopRepository shopRepository) { + this.shopRepository = shopRepository; + } + + public Shop 김밥천국() { + Shop shop = shopRepository.save(Shop.builder() + .name("김밥천국") + .internalName("김천") + .chosung("김") + .phone("010-7574-1212") + .address("천안시 동남구 병천면 1600") + .description("김밥천국입니다.") + .delivery(true) + .deliveryPrice(3000) + .payCard(true) + .payBank(true) + .isDeleted(false) + .isEvent(false) + .remarks("비고") + .hit(0) + .bank("국민") + .accountNumber("01022595923") + .build()); + + ShopOperation shopOperation = ShopOperation.builder() + .isOpen(true) + .isDeleted(false) + .shop(shop) + .build(); + + shop.setShopOperation(shopOperation); + return shop; + } +} From 6d78f13b1809ab5eb40589c1a5bb005993ea5fea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=A0=EA=B4=80=EA=B7=9C?= Date: Wed, 20 Aug 2025 23:04:39 +0900 Subject: [PATCH 15/23] =?UTF-8?q?test:=20=EC=9E=84=EC=8B=9C=20=ED=8F=AC?= =?UTF-8?q?=EC=9E=A5=20=EA=B2=B0=EC=A0=9C=20=EC=A0=95=EB=B3=B4=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=20API=20=ED=86=B5=ED=95=A9=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../acceptance/domain/PaymentApiTest.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/test/java/in/koreatech/payment/acceptance/domain/PaymentApiTest.java b/src/test/java/in/koreatech/payment/acceptance/domain/PaymentApiTest.java index c7131ba..f895659 100644 --- a/src/test/java/in/koreatech/payment/acceptance/domain/PaymentApiTest.java +++ b/src/test/java/in/koreatech/payment/acceptance/domain/PaymentApiTest.java @@ -99,5 +99,29 @@ class TemporaryPaymentSuccess { } """)); } + + @Test + void 임시_포장_결제_정보_저장에_성공한다() throws Exception { + mockMvc.perform( + post("/payments/takeout/temporary") + .header("Authorization", "Bearer " + token) + .contentType(MediaType.APPLICATION_JSON) + .content(""" + { + "phone_number": "01012345678", + "to_owner": "리뷰 이벤트 감사합니다.", + "total_menu_price": 24000, + "provide_cutlery": true, + "total_amount": 24000 + } + """) + ) + .andExpect(status().isOk()) + .andExpect(content().json(""" + { + "order_id": "FAKE_ORDER_123" + } + """)); + } } } From efed4cd35f57c31d5e003ae5656c6d9369347945 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=A0=EA=B4=80=EA=B7=9C?= Date: Wed, 20 Aug 2025 23:45:46 +0900 Subject: [PATCH 16/23] =?UTF-8?q?test:=20=EB=B0=B0=EB=8B=AC=20=EA=B2=B0?= =?UTF-8?q?=EC=A0=9C=20=EC=8A=B9=EC=9D=B8=20API=20=ED=86=B5=ED=95=A9=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/TossPaymentRollBackService.java | 2 +- .../acceptance/domain/PaymentApiTest.java | 87 +++++++++++++++++++ .../{util => mock}/FakeOrderIdGenerator.java | 2 +- 3 files changed, 89 insertions(+), 2 deletions(-) rename src/test/java/in/koreatech/payment/acceptance/{util => mock}/FakeOrderIdGenerator.java (87%) diff --git a/src/main/java/in/koreatech/payment/service/TossPaymentRollBackService.java b/src/main/java/in/koreatech/payment/service/TossPaymentRollBackService.java index 783dfcc..b2d33a9 100644 --- a/src/main/java/in/koreatech/payment/service/TossPaymentRollBackService.java +++ b/src/main/java/in/koreatech/payment/service/TossPaymentRollBackService.java @@ -49,7 +49,7 @@ public class TossPaymentRollBackService implements PaymentRollBackService { private static final String PAYMENT_CANCEL_REASON = "코인 서버 오류로 인한 결제 취소"; @TransactionalEventListener(phase = AFTER_ROLLBACK) - @Transactional(propagation = REQUIRES_NEW, transactionManager = "koinTransactionManager") + @Transactional(propagation = REQUIRES_NEW) public void paymentRollback(TossPaymentRollBackEvent event) { TemporaryPayment temporaryPayment = event.temporaryPayment(); User user = userRepository.getById(temporaryPayment.getUserId()); diff --git a/src/test/java/in/koreatech/payment/acceptance/domain/PaymentApiTest.java b/src/test/java/in/koreatech/payment/acceptance/domain/PaymentApiTest.java index f895659..db0674b 100644 --- a/src/test/java/in/koreatech/payment/acceptance/domain/PaymentApiTest.java +++ b/src/test/java/in/koreatech/payment/acceptance/domain/PaymentApiTest.java @@ -1,5 +1,7 @@ package in.koreatech.payment.acceptance.domain; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.when; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -10,6 +12,7 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.MediaType; import in.koreatech.koin.domain.order.cart.model.Cart; @@ -25,7 +28,10 @@ import in.koreatech.payment.acceptance.fixture.OrderableShopMenuPriceFixture; import in.koreatech.payment.acceptance.fixture.ShopFixture; import in.koreatech.payment.acceptance.fixture.UserFixture; +import in.koreatech.payment.client.TossPaymentClient; +import in.koreatech.payment.client.dto.response.TossPaymentConfirmResponse; import in.koreatech.payment.common.auth.JwtProvider; +import in.koreatech.payment.service.PaymentRollBackService; public class PaymentApiTest extends AcceptanceTest { @@ -50,6 +56,12 @@ public class PaymentApiTest extends AcceptanceTest { @Autowired private OrderableShopMenuPriceFixture orderableShopMenuPriceFixture; + @MockBean + private TossPaymentClient tossPaymentClient; + + @MockBean + private PaymentRollBackService paymentRollBackService; + private User user; private String token; private Shop shop; @@ -123,5 +135,80 @@ class TemporaryPaymentSuccess { } """)); } + + @Test + void 배달_결제_승인에_성공한다() throws Exception { + TossPaymentConfirmResponse confirmDto = new TossPaymentConfirmResponse( + "pay_123", + 24000, + "DONE", + "카드", + "2024-01-01T10:00:00+09:00", + "2024-01-01T10:00:05+09:00" + ); + when(tossPaymentClient.requestConfirm(eq("pay_123"), eq("FAKE_ORDER_123"), eq(24000))) + .thenReturn(confirmDto); + + mockMvc.perform( + post("/payments/delivery/temporary") + .header("Authorization", "Bearer " + token) + .contentType(MediaType.APPLICATION_JSON) + .content(""" + { + "address": "충청남도 천안시 동남구 병천면 충절로 1600 은솔관 422호", + "phone_number": "01012345678", + "to_owner": "리뷰 이벤트 감사합니다.", + "to_rider": "문 앞에 놔주세요.", + "total_menu_price": 24000, + "delivery_tip": 0, + "provide_cutlery": true, + "total_amount": 24000 + } + """) + ) + .andExpect(status().isOk()) + .andExpect(content().json(""" + { + "order_id": "FAKE_ORDER_123" + } + """)); + + mockMvc.perform( + post("/payments/confirm") + .header("Authorization", "Bearer " + token) + .contentType(MediaType.APPLICATION_JSON) + .content(""" + { + "payment_key": "pay_123", + "order_id": "FAKE_ORDER_123", + "amount": 24000 + } + """) + ) + .andExpect(status().isOk()) + .andExpect(content().json(""" + { + "id": 1, + "delivery_address": "충청남도 천안시 동남구 병천면 충절로 1600 은솔관 422호", + "shop_address": "천안시 동남구 병천면 1600", + "to_owner": "리뷰 이벤트 감사합니다.", + "to_rider": "문 앞에 놔주세요.", + "provide_cutlery": true, + "amount": 24000, + "shop_name": "김밥천국", + "menus": [ + { + "name": "김밥", + "quantity": 4, + "options": [] + } + ], + "order_type": "DELIVERY", + "requested_at": "2024.01.01 10:00", + "approved_at": "2024.01.01 10:00", + "payment_method": "카드" + } + """)); + } } } diff --git a/src/test/java/in/koreatech/payment/acceptance/util/FakeOrderIdGenerator.java b/src/test/java/in/koreatech/payment/acceptance/mock/FakeOrderIdGenerator.java similarity index 87% rename from src/test/java/in/koreatech/payment/acceptance/util/FakeOrderIdGenerator.java rename to src/test/java/in/koreatech/payment/acceptance/mock/FakeOrderIdGenerator.java index 79fb874..5e89843 100644 --- a/src/test/java/in/koreatech/payment/acceptance/util/FakeOrderIdGenerator.java +++ b/src/test/java/in/koreatech/payment/acceptance/mock/FakeOrderIdGenerator.java @@ -1,4 +1,4 @@ -package in.koreatech.payment.acceptance.util; +package in.koreatech.payment.acceptance.mock; import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Component; From 13739828f47c9dcefd1799b90da5dbf717512fd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=A0=EA=B4=80=EA=B7=9C?= Date: Wed, 20 Aug 2025 23:50:01 +0900 Subject: [PATCH 17/23] =?UTF-8?q?test:=20=ED=8F=AC=EC=9E=A5=20=EA=B2=B0?= =?UTF-8?q?=EC=A0=9C=20=EC=8A=B9=EC=9D=B8=20API=20=ED=86=B5=ED=95=A9=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../acceptance/domain/PaymentApiTest.java | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/src/test/java/in/koreatech/payment/acceptance/domain/PaymentApiTest.java b/src/test/java/in/koreatech/payment/acceptance/domain/PaymentApiTest.java index db0674b..634f77d 100644 --- a/src/test/java/in/koreatech/payment/acceptance/domain/PaymentApiTest.java +++ b/src/test/java/in/koreatech/payment/acceptance/domain/PaymentApiTest.java @@ -210,5 +210,77 @@ class TemporaryPaymentSuccess { } """)); } + + @Test + void 포장_결제_승인에_성공한다() throws Exception { + TossPaymentConfirmResponse confirmDto = new TossPaymentConfirmResponse( + "pay_123", + 24000, + "DONE", + "카드", + "2024-01-01T10:00:00+09:00", + "2024-01-01T10:00:05+09:00" + ); + when(tossPaymentClient.requestConfirm(eq("pay_123"), eq("FAKE_ORDER_123"), eq(24000))) + .thenReturn(confirmDto); + + mockMvc.perform( + post("/payments/takeout/temporary") + .header("Authorization", "Bearer " + token) + .contentType(MediaType.APPLICATION_JSON) + .content(""" + { + "phone_number": "01012345678", + "to_owner": "리뷰 이벤트 감사합니다.", + "total_menu_price": 24000, + "provide_cutlery": true, + "total_amount": 24000 + } + """) + ) + .andExpect(status().isOk()) + .andExpect(content().json(""" + { + "order_id": "FAKE_ORDER_123" + } + """)); + + mockMvc.perform( + post("/payments/confirm") + .header("Authorization", "Bearer " + token) + .contentType(MediaType.APPLICATION_JSON) + .content(""" + { + "payment_key": "pay_123", + "order_id": "FAKE_ORDER_123", + "amount": 24000 + } + """) + ) + .andExpect(status().isOk()) + .andExpect(content().json(""" + { + "id": 1, + "delivery_address": null, + "shop_address": "천안시 동남구 병천면 1600", + "to_owner": "리뷰 이벤트 감사합니다.", + "to_rider": null, + "provide_cutlery": true, + "amount": 24000, + "shop_name": "김밥천국", + "menus": [ + { + "name": "김밥", + "quantity": 4, + "options": [] + } + ], + "order_type": "TAKE_OUT", + "requested_at": "2024.01.01 10:00", + "approved_at": "2024.01.01 10:00", + "payment_method": "카드" + } + """)); + } } } From 8db6fdc99c6332addcc92c86baa0be66d02ac8a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=A0=EA=B4=80=EA=B7=9C?= Date: Wed, 20 Aug 2025 23:55:09 +0900 Subject: [PATCH 18/23] =?UTF-8?q?feat:=20PaymentIdempotencyKeyFixture=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../fixture/PaymentIdempotencyKeyFixture.java | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 src/test/java/in/koreatech/payment/acceptance/fixture/PaymentIdempotencyKeyFixture.java diff --git a/src/test/java/in/koreatech/payment/acceptance/fixture/PaymentIdempotencyKeyFixture.java b/src/test/java/in/koreatech/payment/acceptance/fixture/PaymentIdempotencyKeyFixture.java new file mode 100644 index 0000000..13e0e58 --- /dev/null +++ b/src/test/java/in/koreatech/payment/acceptance/fixture/PaymentIdempotencyKeyFixture.java @@ -0,0 +1,26 @@ +package in.koreatech.payment.acceptance.fixture; + +import org.springframework.stereotype.Component; + +import in.koreatech.koin.domain.order.model.PaymentIdempotencyKey; +import in.koreatech.koin.domain.order.repository.PaymentIdempotencyKeyRepository; +import in.koreatech.koin.domain.user.model.User; + +@Component +@SuppressWarnings("NonAsciiCharacters") +public class PaymentIdempotencyKeyFixture { + + private final PaymentIdempotencyKeyRepository paymentIdempotencyKeyRepository; + + public PaymentIdempotencyKeyFixture(PaymentIdempotencyKeyRepository paymentIdempotencyKeyRepository) { + this.paymentIdempotencyKeyRepository = paymentIdempotencyKeyRepository; + } + + public PaymentIdempotencyKey 결제_멱등_키(User user, String idempotencyKey) { + return paymentIdempotencyKeyRepository.save(PaymentIdempotencyKey.builder() + .userId(user.getId()) + .idempotencyKey(idempotencyKey) + .build() + ); + } +} From d0f3ae1258edced954379ecbf6cc2fa9482189ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=A0=EA=B4=80=EA=B7=9C?= Date: Thu, 21 Aug 2025 00:08:04 +0900 Subject: [PATCH 19/23] =?UTF-8?q?test:=20=EA=B2=B0=EC=A0=9C=20=EC=B7=A8?= =?UTF-8?q?=EC=86=8C=20API=20=ED=86=B5=ED=95=A9=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../acceptance/domain/PaymentApiTest.java | 122 ++++++++++++++++++ 1 file changed, 122 insertions(+) diff --git a/src/test/java/in/koreatech/payment/acceptance/domain/PaymentApiTest.java b/src/test/java/in/koreatech/payment/acceptance/domain/PaymentApiTest.java index 634f77d..ee8f745 100644 --- a/src/test/java/in/koreatech/payment/acceptance/domain/PaymentApiTest.java +++ b/src/test/java/in/koreatech/payment/acceptance/domain/PaymentApiTest.java @@ -1,5 +1,6 @@ package in.koreatech.payment.acceptance.domain; +import static in.koreatech.payment.client.dto.response.PaymentCancelResponse.*; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.when; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; @@ -7,6 +8,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import java.util.List; +import java.util.UUID; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Nested; @@ -16,6 +18,7 @@ import org.springframework.http.MediaType; import in.koreatech.koin.domain.order.cart.model.Cart; +import in.koreatech.koin.domain.order.model.PaymentIdempotencyKey; import in.koreatech.koin.domain.order.shop.model.entity.menu.OrderableShopMenu; import in.koreatech.koin.domain.order.shop.model.entity.menu.OrderableShopMenuPrice; import in.koreatech.koin.domain.order.shop.model.entity.shop.OrderableShop; @@ -26,9 +29,11 @@ import in.koreatech.payment.acceptance.fixture.OrderableShopFixture; import in.koreatech.payment.acceptance.fixture.OrderableShopMenuFixture; import in.koreatech.payment.acceptance.fixture.OrderableShopMenuPriceFixture; +import in.koreatech.payment.acceptance.fixture.PaymentIdempotencyKeyFixture; import in.koreatech.payment.acceptance.fixture.ShopFixture; import in.koreatech.payment.acceptance.fixture.UserFixture; import in.koreatech.payment.client.TossPaymentClient; +import in.koreatech.payment.client.dto.response.PaymentCancelResponse; import in.koreatech.payment.client.dto.response.TossPaymentConfirmResponse; import in.koreatech.payment.common.auth.JwtProvider; import in.koreatech.payment.service.PaymentRollBackService; @@ -56,6 +61,9 @@ public class PaymentApiTest extends AcceptanceTest { @Autowired private OrderableShopMenuPriceFixture orderableShopMenuPriceFixture; + @Autowired + private PaymentIdempotencyKeyFixture paymentIdempotencyKeyFixture; + @MockBean private TossPaymentClient tossPaymentClient; @@ -64,6 +72,7 @@ public class PaymentApiTest extends AcceptanceTest { private User user; private String token; + private PaymentIdempotencyKey paymentIdempotencyKey; private Shop shop; private OrderableShop orderableShop; private Cart cart; @@ -73,6 +82,8 @@ public class PaymentApiTest extends AcceptanceTest { @BeforeAll void setUp() { user = userFixture.코인_유저(); + // TODO. 프로덕션 리펙토링 이후 생성 객체로 부터 주입받도록 수정 + paymentIdempotencyKey = paymentIdempotencyKeyFixture.결제_멱등_키(user, UUID.randomUUID().toString()); token = jwtProvider.createToken(user); shop = shopFixture.김밥천국(); orderableShop = orderableShopFixture.주문_가능_김밥천국(shop); @@ -282,5 +293,116 @@ class TemporaryPaymentSuccess { } """)); } + + @Test + void 결제_취소에_성공한다() throws Exception { + TossPaymentConfirmResponse confirmDto = new TossPaymentConfirmResponse( + "pay_123", + 24000, + "DONE", + "카드", + "2024-01-01T10:00:00+09:00", + "2024-01-01T10:00:05+09:00" + ); + when(tossPaymentClient.requestConfirm(eq("pay_123"), eq("FAKE_ORDER_123"), eq(24000))) + .thenReturn(confirmDto); + + PaymentCancelResponse cancelDto = new PaymentCancelResponse( + "pay_123", + "FAKE_ORDER_123", + "CANCELED", + List.of(new CancelInfo( + 10000, + "단순 변심이에요", + "2024-01-01T10:00:05+10:00", + "txrd_123" + )) + ); + when(tossPaymentClient.requestCancel(eq("pay_123"), eq("단순 변심"), eq(paymentIdempotencyKey.getIdempotencyKey()))) + .thenReturn(cancelDto); + + mockMvc.perform( + post("/payments/takeout/temporary") + .header("Authorization", "Bearer " + token) + .contentType(MediaType.APPLICATION_JSON) + .content(""" + { + "phone_number": "01012345678", + "to_owner": "리뷰 이벤트 감사합니다.", + "total_menu_price": 24000, + "provide_cutlery": true, + "total_amount": 24000 + } + """) + ) + .andExpect(status().isOk()) + .andExpect(content().json(""" + { + "order_id": "FAKE_ORDER_123" + } + """)); + + mockMvc.perform( + post("/payments/confirm") + .header("Authorization", "Bearer " + token) + .contentType(MediaType.APPLICATION_JSON) + .content(""" + { + "payment_key": "pay_123", + "order_id": "FAKE_ORDER_123", + "amount": 24000 + } + """) + ) + .andExpect(status().isOk()) + .andExpect(content().json(""" + { + "id": 1, + "delivery_address": null, + "shop_address": "천안시 동남구 병천면 1600", + "to_owner": "리뷰 이벤트 감사합니다.", + "to_rider": null, + "provide_cutlery": true, + "amount": 24000, + "shop_name": "김밥천국", + "menus": [ + { + "name": "김밥", + "quantity": 4, + "options": [] + } + ], + "order_type": "TAKE_OUT", + "requested_at": "2024.01.01 10:00", + "approved_at": "2024.01.01 10:00", + "payment_method": "카드" + } + """)); + + mockMvc.perform( + post("/payments/1/cancel") + .header("Authorization", "Bearer " + token) + .header("Idempotency-Key", paymentIdempotencyKey.getIdempotencyKey()) + .contentType(MediaType.APPLICATION_JSON) + .content(""" + { + "cancel_reason": "단순 변심" + } + """) + ) + .andExpect(status().isOk()) + .andExpect(content().json(""" + { + "payment_cancels": [ + { + "id": 1, + "cancel_reason": "단순 변심이에요", + "cancel_amount": 10000, + "canceled_at": "2024.01.01 10:00" + } + ] + } + """)); + } } } From 140d939e76f9381ae885caf7b99bbafd7f19d6f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=A0=EA=B4=80=EA=B7=9C?= Date: Thu, 21 Aug 2025 00:46:54 +0900 Subject: [PATCH 20/23] =?UTF-8?q?test:=20=EB=A6=AC=ED=8C=8C=EC=A7=80?= =?UTF-8?q?=ED=86=A0=EB=A6=AC=20=EA=B2=80=EC=A6=9D=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/PaymentCancelRepository.java | 4 + .../acceptance/domain/PaymentApiTest.java | 179 +++++++++++++++++- 2 files changed, 176 insertions(+), 7 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/order/repository/PaymentCancelRepository.java b/src/main/java/in/koreatech/koin/domain/order/repository/PaymentCancelRepository.java index 2bd1db0..d54dd1e 100644 --- a/src/main/java/in/koreatech/koin/domain/order/repository/PaymentCancelRepository.java +++ b/src/main/java/in/koreatech/koin/domain/order/repository/PaymentCancelRepository.java @@ -1,5 +1,7 @@ package in.koreatech.koin.domain.order.repository; +import java.util.List; + import org.springframework.data.repository.Repository; import in.koreatech.koin.domain.order.model.PaymentCancel; @@ -7,4 +9,6 @@ public interface PaymentCancelRepository extends Repository { void saveAll(Iterable paymentCancels); + + List findAllByPaymentId(Integer paymentId); } diff --git a/src/test/java/in/koreatech/payment/acceptance/domain/PaymentApiTest.java b/src/test/java/in/koreatech/payment/acceptance/domain/PaymentApiTest.java index ee8f745..11cf6ae 100644 --- a/src/test/java/in/koreatech/payment/acceptance/domain/PaymentApiTest.java +++ b/src/test/java/in/koreatech/payment/acceptance/domain/PaymentApiTest.java @@ -1,6 +1,7 @@ package in.koreatech.payment.acceptance.domain; -import static in.koreatech.payment.client.dto.response.PaymentCancelResponse.*; +import static in.koreatech.payment.client.dto.response.PaymentCancelResponse.CancelInfo; +import static org.assertj.core.api.SoftAssertions.assertSoftly; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.when; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; @@ -11,6 +12,7 @@ import java.util.UUID; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -18,7 +20,20 @@ import org.springframework.http.MediaType; import in.koreatech.koin.domain.order.cart.model.Cart; +import in.koreatech.koin.domain.order.model.Order; +import in.koreatech.koin.domain.order.model.OrderDelivery; +import in.koreatech.koin.domain.order.model.OrderMenu; +import in.koreatech.koin.domain.order.model.OrderTakeout; +import in.koreatech.koin.domain.order.model.OrderType; +import in.koreatech.koin.domain.order.model.Payment; +import in.koreatech.koin.domain.order.model.PaymentCancel; import in.koreatech.koin.domain.order.model.PaymentIdempotencyKey; +import in.koreatech.koin.domain.order.model.PaymentMethod; +import in.koreatech.koin.domain.order.model.PaymentStatus; +import in.koreatech.koin.domain.order.repository.OrderMenuRepository; +import in.koreatech.koin.domain.order.repository.PaymentCancelRepository; +import in.koreatech.koin.domain.order.repository.PaymentIdempotencyKeyRepository; +import in.koreatech.koin.domain.order.repository.PaymentRepository; import in.koreatech.koin.domain.order.shop.model.entity.menu.OrderableShopMenu; import in.koreatech.koin.domain.order.shop.model.entity.menu.OrderableShopMenuPrice; import in.koreatech.koin.domain.order.shop.model.entity.shop.OrderableShop; @@ -36,15 +51,29 @@ import in.koreatech.payment.client.dto.response.PaymentCancelResponse; import in.koreatech.payment.client.dto.response.TossPaymentConfirmResponse; import in.koreatech.payment.common.auth.JwtProvider; +import in.koreatech.payment.model.redis.TemporaryPayment; +import in.koreatech.payment.repository.redis.TemporaryPaymentRedisRepository; import in.koreatech.payment.service.PaymentRollBackService; public class PaymentApiTest extends AcceptanceTest { @Autowired - private UserFixture userFixture; + private JwtProvider jwtProvider; @Autowired - private JwtProvider jwtProvider; + private TemporaryPaymentRedisRepository temporaryPaymentRedisRepository; + + @Autowired + private OrderMenuRepository orderMenuRepository; + + @Autowired + private PaymentCancelRepository paymentCancelRepository; + + @Autowired + private PaymentRepository paymentRepository; + + @Autowired + private UserFixture userFixture; @Autowired private CartFixture cartFixture; @@ -120,7 +149,29 @@ class TemporaryPaymentSuccess { { "order_id": "FAKE_ORDER_123" } - """)); + """) + ); + + TemporaryPayment temporaryPayment = temporaryPaymentRedisRepository.getById("FAKE_ORDER_123"); + assertSoftly( + softly -> { + softly.assertThat(temporaryPayment.getOrderId()).isEqualTo("FAKE_ORDER_123"); + softly.assertThat(temporaryPayment.getUserId()).isEqualTo(user.getId()); + softly.assertThat(temporaryPayment.getOrderableShopId()).isEqualTo(orderableShop.getId()); + softly.assertThat(temporaryPayment.getPhoneNumber()).isEqualTo("01012345678"); + softly.assertThat(temporaryPayment.getOrderType()).isEqualTo(OrderType.DELIVERY); + softly.assertThat(temporaryPayment.getAddress()).isEqualTo("충청남도 천안시 동남구 병천면 충절로 1600 은솔관 422호"); + softly.assertThat(temporaryPayment.getToOwner()).isEqualTo("리뷰 이벤트 감사합니다."); + softly.assertThat(temporaryPayment.getToRider()).isEqualTo("문 앞에 놔주세요."); + softly.assertThat(temporaryPayment.getProvideCutlery()).isEqualTo(true); + softly.assertThat(temporaryPayment.getTotalProductPrice()).isEqualTo(24000); + softly.assertThat(temporaryPayment.getDeliveryFee()).isEqualTo(0); + softly.assertThat(temporaryPayment.getTotalPrice()).isEqualTo(24000); + softly.assertThat(temporaryPayment.getTemporaryMenuItems().get(0).name()).isEqualTo("김밥"); + softly.assertThat(temporaryPayment.getTemporaryMenuItems().get(0).quantity()).isEqualTo(4); + softly.assertThat(temporaryPayment.getTemporaryMenuItems().get(0).options()).isNull(); + } + ); } @Test @@ -145,6 +196,27 @@ class TemporaryPaymentSuccess { "order_id": "FAKE_ORDER_123" } """)); + + TemporaryPayment temporaryPayment = temporaryPaymentRedisRepository.getById("FAKE_ORDER_123"); + assertSoftly( + softly -> { + softly.assertThat(temporaryPayment.getOrderId()).isEqualTo("FAKE_ORDER_123"); + softly.assertThat(temporaryPayment.getUserId()).isEqualTo(user.getId()); + softly.assertThat(temporaryPayment.getOrderableShopId()).isEqualTo(orderableShop.getId()); + softly.assertThat(temporaryPayment.getPhoneNumber()).isEqualTo("01012345678"); + softly.assertThat(temporaryPayment.getOrderType()).isEqualTo(OrderType.TAKE_OUT); + softly.assertThat(temporaryPayment.getAddress()).isNull(); + softly.assertThat(temporaryPayment.getToOwner()).isEqualTo("리뷰 이벤트 감사합니다."); + softly.assertThat(temporaryPayment.getToRider()).isNull(); + softly.assertThat(temporaryPayment.getProvideCutlery()).isEqualTo(true); + softly.assertThat(temporaryPayment.getTotalProductPrice()).isEqualTo(24000); + softly.assertThat(temporaryPayment.getDeliveryFee()).isNull(); + softly.assertThat(temporaryPayment.getTotalPrice()).isEqualTo(24000); + softly.assertThat(temporaryPayment.getTemporaryMenuItems().get(0).name()).isEqualTo("김밥"); + softly.assertThat(temporaryPayment.getTemporaryMenuItems().get(0).quantity()).isEqualTo(4); + softly.assertThat(temporaryPayment.getTemporaryMenuItems().get(0).options()).isNull(); + } + ); } @Test @@ -182,7 +254,8 @@ class TemporaryPaymentSuccess { { "order_id": "FAKE_ORDER_123" } - """)); + """) + ); mockMvc.perform( post("/payments/confirm") @@ -219,7 +292,45 @@ class TemporaryPaymentSuccess { "approved_at": "2024.01.01 10:00", "payment_method": "카드" } - """)); + """) + ); + + Payment payment = paymentRepository.getById(user.getId()); + Order order = payment.getOrder(); + OrderTakeout orderTakeout = order.getOrderTakeout(); + OrderDelivery orderDelivery = order.getOrderDelivery(); + List orderMenus = orderMenuRepository.findAllByOrderId(order.getId()); + + assertSoftly( + softly -> { + softly.assertThat(payment.getId()).isEqualTo(1); + softly.assertThat(payment.getPaymentKey()).isEqualTo("pay_123"); + softly.assertThat(payment.getAmount()).isEqualTo(24000); + softly.assertThat(payment.getPaymentStatus()).isEqualTo(PaymentStatus.DONE); + softly.assertThat(payment.getPaymentMethod()).isEqualTo(PaymentMethod.CARD); + + softly.assertThat(order.getId()).isEqualTo("FAKE_ORDER_123"); + softly.assertThat(order.getOrderType()).isEqualTo(OrderType.DELIVERY); + softly.assertThat(order.getPhoneNumber()).isEqualTo("01012345678"); + softly.assertThat(order.getTotalProductPrice()).isEqualTo(24000); + softly.assertThat(order.getTotalPrice()).isEqualTo(24000); + + softly.assertThat(orderTakeout).isNull(); + + softly.assertThat(orderDelivery.getToOwner()).isEqualTo("리뷰 이벤트 감사합니다."); + softly.assertThat(orderDelivery.getToRider()).isEqualTo("문 앞에 놔주세요."); + softly.assertThat(orderDelivery.getDeliveryTip()).isEqualTo(0); + softly.assertThat(orderDelivery.getProvideCutlery()).isEqualTo(true); + + softly.assertThat(orderMenus).hasSize(1); + softly.assertThat(orderMenus.get(0).getMenuName()).isEqualTo("김밥"); + softly.assertThat(orderMenus.get(0).getMenuPrice()).isEqualTo(6000); + softly.assertThat(orderMenus.get(0).getMenuPriceName()).isEqualTo("소고기 김밥"); + softly.assertThat(orderMenus.get(0).getId()).isEqualTo(1); + softly.assertThat(orderMenus.get(0).getQuantity()).isEqualTo(4); + softly.assertThat(orderMenus.get(0).getOrderMenuOptions()).isEmpty(); + } + ); } @Test @@ -292,6 +403,41 @@ class TemporaryPaymentSuccess { "payment_method": "카드" } """)); + + Payment payment = paymentRepository.getById(user.getId()); + Order order = payment.getOrder(); + OrderTakeout orderTakeout = order.getOrderTakeout(); + OrderDelivery orderDelivery = order.getOrderDelivery(); + List orderMenus = orderMenuRepository.findAllByOrderId(order.getId()); + + assertSoftly( + softly -> { + softly.assertThat(payment.getId()).isEqualTo(1); + softly.assertThat(payment.getPaymentKey()).isEqualTo("pay_123"); + softly.assertThat(payment.getAmount()).isEqualTo(24000); + softly.assertThat(payment.getPaymentStatus()).isEqualTo(PaymentStatus.DONE); + softly.assertThat(payment.getPaymentMethod()).isEqualTo(PaymentMethod.CARD); + + softly.assertThat(order.getId()).isEqualTo("FAKE_ORDER_123"); + softly.assertThat(order.getOrderType()).isEqualTo(OrderType.TAKE_OUT); + softly.assertThat(order.getPhoneNumber()).isEqualTo("01012345678"); + softly.assertThat(order.getTotalProductPrice()).isEqualTo(24000); + softly.assertThat(order.getTotalPrice()).isEqualTo(24000); + + softly.assertThat(orderTakeout.getToOwner()).isEqualTo("리뷰 이벤트 감사합니다."); + softly.assertThat(orderTakeout.getProvideCutlery()).isEqualTo(true); + + softly.assertThat(orderDelivery).isNull(); + + softly.assertThat(orderMenus).hasSize(1); + softly.assertThat(orderMenus.get(0).getMenuName()).isEqualTo("김밥"); + softly.assertThat(orderMenus.get(0).getMenuPrice()).isEqualTo(6000); + softly.assertThat(orderMenus.get(0).getMenuPriceName()).isEqualTo("소고기 김밥"); + softly.assertThat(orderMenus.get(0).getId()).isEqualTo(1); + softly.assertThat(orderMenus.get(0).getQuantity()).isEqualTo(4); + softly.assertThat(orderMenus.get(0).getOrderMenuOptions()).isEmpty(); + } + ); } @Test @@ -318,7 +464,8 @@ class TemporaryPaymentSuccess { "txrd_123" )) ); - when(tossPaymentClient.requestCancel(eq("pay_123"), eq("단순 변심"), eq(paymentIdempotencyKey.getIdempotencyKey()))) + when(tossPaymentClient.requestCancel(eq("pay_123"), eq("단순 변심"), + eq(paymentIdempotencyKey.getIdempotencyKey()))) .thenReturn(cancelDto); mockMvc.perform( @@ -403,6 +550,24 @@ class TemporaryPaymentSuccess { ] } """)); + + Payment payment = paymentRepository.getById(user.getId()); + List paymentCancels = paymentCancelRepository.findAllByPaymentId(payment.getId()); + + assertSoftly( + softly -> { + softly.assertThat(payment.getId()).isEqualTo(1); + softly.assertThat(payment.getPaymentKey()).isEqualTo("pay_123"); + softly.assertThat(payment.getAmount()).isEqualTo(24000); + softly.assertThat(payment.getPaymentStatus()).isEqualTo(PaymentStatus.CANCELED); + softly.assertThat(payment.getPaymentMethod()).isEqualTo(PaymentMethod.CARD); + + softly.assertThat(paymentCancels.size()).isEqualTo(1); + softly.assertThat(paymentCancels.get(0).getId()).isEqualTo(1); + softly.assertThat(paymentCancels.get(0).getCancelReason()).isEqualTo("단순 변심이에요"); + softly.assertThat(paymentCancels.get(0).getCancelAmount()).isEqualTo(10000); + } + ); } } } From a703e5fdf05a7b52bbdcf506011daaf9f9dcf007 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=A0=EA=B4=80=EA=B7=9C?= Date: Thu, 21 Aug 2025 00:48:32 +0900 Subject: [PATCH 21/23] =?UTF-8?q?test:=20=EA=B2=B0=EC=A0=9C=20API=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=ED=81=B4=EB=9E=98=EC=8A=A4=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../koreatech/payment/acceptance/domain/PaymentApiTest.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/test/java/in/koreatech/payment/acceptance/domain/PaymentApiTest.java b/src/test/java/in/koreatech/payment/acceptance/domain/PaymentApiTest.java index 11cf6ae..9b21355 100644 --- a/src/test/java/in/koreatech/payment/acceptance/domain/PaymentApiTest.java +++ b/src/test/java/in/koreatech/payment/acceptance/domain/PaymentApiTest.java @@ -123,6 +123,7 @@ void setUp() { } @Nested + @DisplayName("임시 결제 정보 저장 API - 성공") class TemporaryPaymentSuccess { @Test @@ -218,6 +219,11 @@ class TemporaryPaymentSuccess { } ); } + } + + @Nested + @DisplayName("결제 API - 성공") + class PaymentSuccess { @Test void 배달_결제_승인에_성공한다() throws Exception { From 22cd2d3b9299ac21469e5d44745037eb15e1e99e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=A0=EA=B4=80=EA=B7=9C?= Date: Thu, 21 Aug 2025 00:48:59 +0900 Subject: [PATCH 22/23] =?UTF-8?q?chore:=20=EC=BD=94=EB=93=9C=20=ED=8F=AC?= =?UTF-8?q?=EB=A9=A7=ED=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../in/koreatech/payment/acceptance/domain/PaymentApiTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/java/in/koreatech/payment/acceptance/domain/PaymentApiTest.java b/src/test/java/in/koreatech/payment/acceptance/domain/PaymentApiTest.java index 9b21355..7d39754 100644 --- a/src/test/java/in/koreatech/payment/acceptance/domain/PaymentApiTest.java +++ b/src/test/java/in/koreatech/payment/acceptance/domain/PaymentApiTest.java @@ -32,7 +32,6 @@ import in.koreatech.koin.domain.order.model.PaymentStatus; import in.koreatech.koin.domain.order.repository.OrderMenuRepository; import in.koreatech.koin.domain.order.repository.PaymentCancelRepository; -import in.koreatech.koin.domain.order.repository.PaymentIdempotencyKeyRepository; import in.koreatech.koin.domain.order.repository.PaymentRepository; import in.koreatech.koin.domain.order.shop.model.entity.menu.OrderableShopMenu; import in.koreatech.koin.domain.order.shop.model.entity.menu.OrderableShopMenuPrice; From d0abd7f919ffde2d60ada55524ceffc15aa8846b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=A0=EA=B4=80=EA=B7=9C?= Date: Thu, 21 Aug 2025 00:53:19 +0900 Subject: [PATCH 23/23] =?UTF-8?q?feat:=20=EC=A3=BC=EB=AC=B8=20=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=EC=83=9D=EC=84=B1=20=ED=81=B4=EB=9E=98=EC=8A=A4=20?= =?UTF-8?q?MockBean=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../payment/acceptance/domain/PaymentApiTest.java | 12 ++++++++++++ .../acceptance/mock/FakeOrderIdGenerator.java | 15 --------------- 2 files changed, 12 insertions(+), 15 deletions(-) delete mode 100644 src/test/java/in/koreatech/payment/acceptance/mock/FakeOrderIdGenerator.java diff --git a/src/test/java/in/koreatech/payment/acceptance/domain/PaymentApiTest.java b/src/test/java/in/koreatech/payment/acceptance/domain/PaymentApiTest.java index 7d39754..43a5542 100644 --- a/src/test/java/in/koreatech/payment/acceptance/domain/PaymentApiTest.java +++ b/src/test/java/in/koreatech/payment/acceptance/domain/PaymentApiTest.java @@ -2,6 +2,7 @@ import static in.koreatech.payment.client.dto.response.PaymentCancelResponse.CancelInfo; import static org.assertj.core.api.SoftAssertions.assertSoftly; +import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.when; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; @@ -53,6 +54,7 @@ import in.koreatech.payment.model.redis.TemporaryPayment; import in.koreatech.payment.repository.redis.TemporaryPaymentRedisRepository; import in.koreatech.payment.service.PaymentRollBackService; +import in.koreatech.payment.util.OrderIdGenerator; public class PaymentApiTest extends AcceptanceTest { @@ -98,6 +100,9 @@ public class PaymentApiTest extends AcceptanceTest { @MockBean private PaymentRollBackService paymentRollBackService; + @MockBean + private OrderIdGenerator orderIdGenerator; + private User user; private String token; private PaymentIdempotencyKey paymentIdempotencyKey; @@ -127,6 +132,8 @@ class TemporaryPaymentSuccess { @Test void 임시_배달_결제_정보_저장에_성공한다() throws Exception { + given(orderIdGenerator.generateOrderId()).willReturn("FAKE_ORDER_123"); + mockMvc.perform( post("/payments/delivery/temporary") .header("Authorization", "Bearer " + token) @@ -176,6 +183,8 @@ class TemporaryPaymentSuccess { @Test void 임시_포장_결제_정보_저장에_성공한다() throws Exception { + given(orderIdGenerator.generateOrderId()).willReturn("FAKE_ORDER_123"); + mockMvc.perform( post("/payments/takeout/temporary") .header("Authorization", "Bearer " + token) @@ -236,6 +245,7 @@ class PaymentSuccess { ); when(tossPaymentClient.requestConfirm(eq("pay_123"), eq("FAKE_ORDER_123"), eq(24000))) .thenReturn(confirmDto); + given(orderIdGenerator.generateOrderId()).willReturn("FAKE_ORDER_123"); mockMvc.perform( post("/payments/delivery/temporary") @@ -350,6 +360,7 @@ class PaymentSuccess { ); when(tossPaymentClient.requestConfirm(eq("pay_123"), eq("FAKE_ORDER_123"), eq(24000))) .thenReturn(confirmDto); + given(orderIdGenerator.generateOrderId()).willReturn("FAKE_ORDER_123"); mockMvc.perform( post("/payments/takeout/temporary") @@ -457,6 +468,7 @@ class PaymentSuccess { ); when(tossPaymentClient.requestConfirm(eq("pay_123"), eq("FAKE_ORDER_123"), eq(24000))) .thenReturn(confirmDto); + given(orderIdGenerator.generateOrderId()).willReturn("FAKE_ORDER_123"); PaymentCancelResponse cancelDto = new PaymentCancelResponse( "pay_123", diff --git a/src/test/java/in/koreatech/payment/acceptance/mock/FakeOrderIdGenerator.java b/src/test/java/in/koreatech/payment/acceptance/mock/FakeOrderIdGenerator.java deleted file mode 100644 index 5e89843..0000000 --- a/src/test/java/in/koreatech/payment/acceptance/mock/FakeOrderIdGenerator.java +++ /dev/null @@ -1,15 +0,0 @@ -package in.koreatech.payment.acceptance.mock; - -import org.springframework.context.annotation.Primary; -import org.springframework.stereotype.Component; - -import in.koreatech.payment.util.OrderIdGenerator; - -@Component -@Primary -class FakeOrderIdGenerator implements OrderIdGenerator { - @Override - public String generateOrderId() { - return "FAKE_ORDER_123"; - } -}