@@ -0,0 +1,98 @@
+package store.controller;
+
+import java.util.List;
+import store.constant.OrderStatus;
+import store.dto.OrderNotice;
+import store.dto.PurchaseInfo;
+import store.dto.Receipt;
+import store.service.StoreService;
+import store.view.InputView;
+import store.view.OutputView;
+
+public class StoreController {
+    private final InputView inputView;
+    private final OutputView outputView;
+    private final StoreService storeService;
+
+    public StoreController(InputView inputView, OutputView outputView, StoreService storeService) {
+        this.inputView = inputView;
+        this.outputView = outputView;
+        this.storeService = storeService;
+    }
+
+    public void run() {
+        do {
+            runStore();
+        } while (wantMorePurchase());
+    }
+
+    private void runStore() {
+        outputView.printWelcomeMessage();
+        outputView.printProductInventory(storeService.getProducts());
+        enterOrderInfo();
+        checkOrders();
+        outputView.printReceipt(calculateOrders());
+    }
+
+    private void enterOrderInfo() {
+        try {
+            storeService.resetOrders();
+            List<PurchaseInfo> purchases = inputView.readPurchases();
+            storeService.applyPurchaseInfo(purchases);
+        } catch (IllegalArgumentException e) {
+            outputView.printErrorMessage(e.getMessage());
+            enterOrderInfo();
+        }
+    }
+
+    private void checkOrders() {
+        while (storeService.hasNextOrder()) {
+            OrderNotice orderNotice = storeService.checkOrder();
+            OrderStatus orderStatus = orderNotice.orderStatus();
+
+            proceedOrder(orderNotice, orderStatus);
+        }
+    }
+
+    private void proceedOrder(OrderNotice orderNotice, OrderStatus orderStatus) {
+        if (orderStatus != OrderStatus.NOT_APPLICABLE) {
+            try {
+                askAndApplyUserDecision(orderNotice, orderStatus);
+            } catch (IllegalArgumentException e) {
+                outputView.printErrorMessage(e.getMessage());
+                askAndApplyUserDecision(orderNotice, orderStatus);
+            }
+        }
+    }
+
+    private void askAndApplyUserDecision(OrderNotice orderNotice, OrderStatus orderStatus) {
+        if (orderStatus == OrderStatus.PROMOTION_AVAILABLE_ADDITIONAL_PRODUCT) {
+            if (inputView.askAddFreeProduct(orderNotice)) {
+                storeService.modifyOrder(orderNotice.quantity());
+            }
+        }
+        if (orderStatus == OrderStatus.PROMOTION_STOCK_INSUFFICIENT) {
+            if (!inputView.askPurchaseWithoutPromotion(orderNotice)) {
+                storeService.modifyOrder(-orderNotice.quantity());
+            }
+        }
+    }
+
+    private Receipt calculateOrders() {
+        try {
+            return storeService.calculateOrders(inputView.askForMembershipDiscount());
+        } catch (IllegalArgumentException e) {
+            outputView.printErrorMessage(e.getMessage());
+            return calculateOrders();
+        }
+    }
+
+    private boolean wantMorePurchase() {
+        try {
+            return inputView.askForAdditionalPurchase();
+        } catch (IllegalArgumentException e) {
+            outputView.printErrorMessage(e.getMessage());
+            return wantMorePurchase();
+        }
+    }
+}

리뷰: 만약 예외가 계속 발생할 경우 call stack이 가득차는 경우가 생길수도 있을 것 같습니다. 재귀가 아닌 반복문을 사용하여 이를 방지하는 것은 어떨까요?