@@ -0,0 +1,158 @@
+package store.service;
+
+import static store.util.Constant.ANSWER_NO;
+import static store.util.Constant.ANSWER_YES;
+import static store.util.Validator.validateAnswer;
+import static store.view.InputView.readForAdditionalItem;
+import static store.view.InputView.readForMembershipDiscount;
+import static store.view.InputView.readForOutOfStock;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import store.domain.Inventory;
+import store.domain.Order;
+import store.domain.Product;
+import store.domain.Promotion;
+import store.domain.Promotions;
+import store.domain.Receipt;
+import store.view.OutputView;
+
+public class PaymentService {
+
+    private final Map<String, Integer> shoppingCart;
+    private final Receipt receipt;
+
+    public PaymentService(Inventory inventory, Promotions promotions, Order order) {
+        shoppingCart = new LinkedHashMap<>();
+        receipt = new Receipt();
+        checkPromotionProduct(inventory, promotions, order);
+        updatePurchaseHistory(inventory);
+        updateGiveAwayHistory(inventory, promotions, order);
+        updateInventory(inventory, promotions);
+        checkMembershipDiscount(promotions);
+    }
+
+    private void checkMembershipDiscount(Promotions promotions) {
+        String answer = inputForMembershipDiscount();
+        if (answer.equals(ANSWER_YES)) {
+            receipt.applyMembershipDiscount(promotions);
+        }
+    }
+
+    private void updateInventory(Inventory inventory, Promotions promotions) {
+        shoppingCart.forEach((productName, quantity) -> {
+            Product product = inventory.findProductWithPromotion(productName);
+            if (product != null && promotions.isPromotionApplicable(product)) {
+                inventory.reducePromotionStock(productName, quantity);
+                return;
+            }
+            inventory.reduceNotPromotionStock(productName, quantity);
+        });
+    }
+
+    private void updatePurchaseHistory(Inventory inventory) {
+        shoppingCart.forEach((productName, quantity) -> {
+            Product product = inventory.findProductWithPromotion(productName);
+            if (product == null) {
+                product = inventory.findProductWithoutPromotion(productName);
+            }
+            receipt.addPurchaseHistory(product, quantity);
+        });
+    }
+
+    private void updateGiveAwayHistory(Inventory inventory, Promotions promotions, Order order) {
+        shoppingCart.forEach((productName, quantity) -> {
+            Product product = inventory.findProductWithPromotion(productName);
+            if (product != null && promotions.isPromotionApplicable(product)) {
+                Promotion promotion = promotions.findPromotion(product.getPromotionName());
+                receipt.addGiveAwayHistory(product, promotion, quantity);
+            }
+        });
+    }
+
+    private void checkPromotionProduct(Inventory inventory, Promotions promotions, Order order) {
+        Map<String, Integer> orders = order.getOrders();
+        orders.forEach((productName, quantity) -> {
+            shoppingCart.put(productName, quantity);
+            Product product = inventory.findProductWithPromotion(productName);
+            if (product != null && promotions.isPromotionApplicable(product)) {
+                Promotion promotion = promotions.findPromotion(product.getPromotionName());
+                checkBenefitOrOutOfStock(product, promotion, Map.entry(productName, quantity));
+            }
+        });
+    }
+
+    private void checkBenefitOrOutOfStock(Product product, Promotion promotion, Map.Entry<String, Integer> order) {
+        String productName = order.getKey();
+        int quantity = order.getValue();
+        if (canApplyPromotionBenefit(product, promotion, Map.entry(productName, quantity))) {
+            if (inputForAdditionalItem(productName).equals(ANSWER_YES)) {
+                shoppingCart.put(productName, quantity + 1);
+            }
+            return;
+        }
+        checkOutOfStock(product, promotion, Map.entry(productName, quantity));
+    }
+
+    private boolean canApplyPromotionBenefit(Product product, Promotion promotion, Map.Entry<String, Integer> order) {
+        int bundle = promotion.calculateBundle();
+        int quantity = order.getValue();
+        if (quantity < product.getQuantity()) {
+            return quantity % bundle == bundle - 1;
+        }
+        return false;
+    }
+
+    private void checkOutOfStock(Product product, Promotion promotion, Map.Entry<String, Integer> order) {
+        int bundle = promotion.calculateBundle();
+        int shareOfOrder = order.getValue() / bundle;
+        int shareOfProduct = product.getQuantity() / bundle;
+        int shortageQuantity = order.getValue() - (Math.min(shareOfOrder, shareOfProduct) * bundle);
+        if (shortageQuantity != 0) {
+            String answer = inputForOutOfStock(order.getKey(), shortageQuantity);
+            if (answer.equals(ANSWER_NO)) {
+                shoppingCart.put(order.getKey(), order.getValue() - shortageQuantity);
+            }
+        }
+    }
+
+    private String inputForOutOfStock(String productName, int shortageQuantity) {
+        while (true) {
+            try {
+                String answer = readForOutOfStock(productName, shortageQuantity);
+                validateAnswer(answer);
+                return answer;
+            } catch (IllegalArgumentException e) {
+                OutputView.printError(e.getMessage());
+            }
+        }
+    }
+
+    private String inputForAdditionalItem(String productName) {
+        while (true) {
+            try {
+                String answer = readForAdditionalItem(productName);
+                validateAnswer(answer);
+                return answer;
+            } catch (IllegalArgumentException e) {
+                OutputView.printError(e.getMessage());
+            }
+        }
+    }
+
+    private String inputForMembershipDiscount() {
+        while (true) {
+            try {
+                String answer = readForMembershipDiscount();
+                validateAnswer(answer);
+                return answer;
+            } catch (IllegalArgumentException e) {
+                OutputView.printError(e.getMessage());
+            }
+        }
+    }
+
+    public Receipt getReceipt() {
+        return receipt;
+    }
+}

리뷰: 저는 service에서 input, output을 하는 건 역할에 안맞다고 생각합니다. controller가 사용자에게 입력과 출력을 담당한다고 생각하는데 service에서 하는 역할이 아니라고 생각합니다