@@ -0,0 +1,130 @@
+package store.view.formatter;
+
+import store.model.order.Payment;
+import store.model.store.StoreRoom;
+import store.model.store.product.Product;
+import store.model.store.product.Products;
+
+public class OutputFormatter {
+    private static final String WELCOME_MESSAGE = "안녕하세요. W편의점입니다.\n현재 보유하고 있는 상품입니다.";
+    private static final String PRODUCT_PREFIX = "- ";
+    private static final String SOLD_OUT = "재고 없음";
+    private static final String RECEIPT_START_MESSAGE = "==============W 편의점================";
+    private static final String RECEIPT_COLUMN_MESSAGE = "상품명\t\t수량\t금액";
+    private static final String RECEIPT_PURCHASE_MESSAGE_FORMAT = "%s\t\t%,d \t%,d";
+    private static final String RECEIPT_GIVEAWAY_MESSAGE = "=============증\t정===============";
+    private static final String RECEIPT_GIVEAWAY_PRODUCT_MESSAGE_FORMAT = "%s\t\t%,d";
+    private static final String RECEIPT_LINE_SEPARATOR = "====================================";
+    private static final String RECEIPT_TOTAL_PRICE_MESSAGE_FORMAT = "총구매액\t\t%,d\t%,d";
+    private static final String RECEIPT_PROMOTION_DISCOUNT_MESSAGE_FORMAT = "행사할인\t\t\t-%,d";
+    private static final String RECEIPT_MEMBERSHIP_DISCOUNT_MESSAGE_FORMAT = "멤버십할인\t\t\t-%,d";
+    private static final String RECEIPT_ACTUAL_PRICE_MESSAGE_FORMAT = "내실돈\t\t\t%,d";
+    private static final String SYSTEM_LINE_SEPARATOR = System.lineSeparator();
+
+    private OutputFormatter() {
+    }
+
+    public static String buildStoreStock(StoreRoom storeRoom) {
+        StringBuilder sb = new StringBuilder();
+        sb.append(WELCOME_MESSAGE).append(SYSTEM_LINE_SEPARATOR).append(SYSTEM_LINE_SEPARATOR);
+
+        String menus = buildProducts(storeRoom.getGeneralProducts(), storeRoom.getPromotionProducts());
+        sb.append(menus);
+        return sb.toString();
+    }
+
+    public static String buildReceipt(Payment payment) {
+        StringBuilder sb = new StringBuilder();
+        sb.append(buildReceiptHeader())
+                .append(buildPurchaseItems(payment))
+                .append(buildGiveawayIems(payment))
+                .append(buildReceiptSummary(payment));
+        return sb.toString();
+    }
+
+    private static String buildProducts(Products generalProducts, Products promotionProducts) {
+        StringBuilder sb = new StringBuilder();
+        for (Product product : generalProducts.getProducts()) {
+            if (promotionProducts.contains(product.getName())) {
+                sb.append(buildProductMessage(promotionProducts.findNullableProductByName(product.getName())));
+            }
+            sb.append(buildProductMessage(product));
+        }
+        return sb.toString();
+    }
+
+    private static String buildProductMessage(Product product) {
+        StringBuilder sb = new StringBuilder();
+        sb.append(PRODUCT_PREFIX)
+                .append(product.getName()).append(" ")
+                .append(String.format("%,d원", product.getPrice())).append(" ")
+                .append(buildProductStock(product.getStock())).append(" ")
+                .append(product.getPromotionName()).append(SYSTEM_LINE_SEPARATOR);
+        return sb.toString();
+    }
+
+    private static String buildProductStock(int stock) {
+        if (stock == 0) {
+            return SOLD_OUT;
+        }
+        return String.format("%,d개", stock);
+    }
+
+    private static String buildReceiptHeader() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(RECEIPT_START_MESSAGE).append(SYSTEM_LINE_SEPARATOR)
+                .append(RECEIPT_COLUMN_MESSAGE).append(SYSTEM_LINE_SEPARATOR);
+        return sb.toString();
+    }
+
+    private static String buildPurchaseItems(Payment payment) {
+        StringBuilder sb = new StringBuilder();
+        payment.getPurchaseOrder().getPurchaseOrder().forEach((name, quantity) ->
+                sb.append(String.format(RECEIPT_PURCHASE_MESSAGE_FORMAT,
+                                name, quantity, payment.calculateProductPrice(name)))
+                        .append(SYSTEM_LINE_SEPARATOR)
+        );
+        return sb.toString();
+    }
+
+    private static String buildGiveawayIems(Payment payment) {
+        StringBuilder sb = new StringBuilder();
+        sb.append(RECEIPT_GIVEAWAY_MESSAGE).append(SYSTEM_LINE_SEPARATOR);
+
+        payment.extractPromotionalProducts().forEach((name, quantity) ->
+                sb.append(String.format(RECEIPT_GIVEAWAY_PRODUCT_MESSAGE_FORMAT, name, quantity))
+                        .append(SYSTEM_LINE_SEPARATOR)
+        );
+        return sb.toString();
+    }
+
+    private static String buildReceiptSummary(Payment payment) {
+        StringBuilder sb = new StringBuilder();
+        sb.append(RECEIPT_LINE_SEPARATOR).append(SYSTEM_LINE_SEPARATOR)
+                .append(buildTotalPrice(payment))
+                .append(buildDiscounts(payment))
+                .append(buildFinalPrice(payment));
+        return sb.toString();
+    }
+
+    private static String buildTotalPrice(Payment payment) {
+        return String.format(RECEIPT_TOTAL_PRICE_MESSAGE_FORMAT,
+                payment.calculateTotalBuyAmount(), payment.calculateTotalPrice())
+                + SYSTEM_LINE_SEPARATOR;
+    }
+
+    private static String buildDiscounts(Payment payment) {
+        return String.format(RECEIPT_PROMOTION_DISCOUNT_MESSAGE_FORMAT,
+                payment.calculatePromotionalDiscount())
+                + SYSTEM_LINE_SEPARATOR
+                + String.format(RECEIPT_MEMBERSHIP_DISCOUNT_MESSAGE_FORMAT,
+                payment.calculateMembershipDiscount())
+                + SYSTEM_LINE_SEPARATOR;
+    }
+
+    private static String buildFinalPrice(Payment payment) {
+        return String.format(RECEIPT_ACTUAL_PRICE_MESSAGE_FORMAT,
+                payment.calculateActualPrice())
+                + SYSTEM_LINE_SEPARATOR;
+    }
+}

리뷰: `getPurchaseOrder`라는 메소드가 두 번 연속되어 모호한 감이 있는 것 같습니다! 
두 메소드의 네이밍을 달리 하면 좋을 것 같아요!

추가적으로 첫 `getPurchaseOrder` 에서는 `PurchaseOrder` 객체가 반환되고 두 번째에서는 아이템 정보들이 담긴 `Map`이 반환되는 것 같습니다! 이는 `디미터의 법칙`에 위배되는 것으로 보이는데요! 우테코에서 제시하는 클린코드 사항에 `디미터 법칙`이 포함된 만큼 연속적으로 사용하는 것을 지양하는 것도 좋을 것 같아요!