@@ -0,0 +1,126 @@
+package store.view;
+
+import java.text.NumberFormat;
+import store.domain.Product;
+import store.domain.Products;
+import store.dto.PurchaseDTO;
+import store.dto.ReceiptDTO;
+
+public class OutputView {
+    private static final NumberFormat format = NumberFormat.getNumberInstance();
+
+    public static void printProducts(Products products) {
+        System.out.println(OutputMessage.OUTPUT_HELLO_CONVENIENCE_MESSAGE);
+        for (String product : products.getNames()) {
+            Product with = products.getItemWithPromotion(product);
+            Product without = products.getItemWithoutPromotion(product);
+
+            printWith(with);
+            printWithout(without, with);
+        }
+        System.out.println();
+    }
+
+    private static void printWith(Product with) {
+        if (with != null) {
+            String price = getPrice(with);
+            String quantity = getQuantity(with);
+            String promotion = getPromotion(with);
+            System.out.println("- " + with.getName() + " " + price + " " + quantity + " " + promotion);
+        }
+    }
+
+    private static void printWithout(Product without, Product with) {
+        if (without == null) {
+            String price = getPrice(with);
+            System.out.println("- " + with.getName() + " " + price + " 재고 없음");
+            return;
+        }
+        String price = getPrice(without);
+        String quantity = getQuantity(without);
+        System.out.println("- " + without.getName() + " " + price + " " + quantity);
+    }
+
+    public static void printReceipt(ReceiptDTO receiptDTO, boolean isMembership) {
+        printPurchaseItems(receiptDTO);
+        printFreeItems(receiptDTO);
+        printAccount(receiptDTO, isMembership);
+    }
+
+    private static void printAccount(ReceiptDTO receiptDTO, boolean isMembership) {
+        System.out.println(OutputMessage.OUTPUT_RECEIPT_BOTTOM_MESSAGE);
+        printTotalPrice(receiptDTO);
+        printPromotionDiscount(receiptDTO);
+        printMembershipDiscount(receiptDTO, isMembership);
+        printPayment(receiptDTO, isMembership);
+    }
+
+    private static void printPayment(ReceiptDTO receiptDTO, boolean isMembership) {
+        System.out.printf(OutputMessage.OUTPUT_RECEIPT_PAY_MESSAGE.format(
+                OutputMessage.TO_BE_PAID,
+                format.format(totalPayment(receiptDTO, isMembership))));
+        System.out.println();
+    }
+
+    private static int totalPayment(ReceiptDTO receiptDTO, boolean isMembership) {
+        return receiptDTO.getTotalAmount() - receiptDTO.getFreeAmount() - receiptDTO.getMembershipAmount(isMembership);
+    }
+
+    private static void printMembershipDiscount(ReceiptDTO receiptDTO, boolean isMembership) {
+        System.out.printf(OutputMessage.OUTPUT_RECEIPT_DISCOUNT_MESSAGE.format(
+                OutputMessage.MEMBERSHIP_DISCOUNT,
+                "-" + format.format(receiptDTO.getMembershipAmount(isMembership))));
+    }
+
+    private static void printPromotionDiscount(ReceiptDTO receiptDTO) {
+        System.out.printf(OutputMessage.OUTPUT_RECEIPT_DISCOUNT_MESSAGE.format(
+                OutputMessage.PROMOTION_DISCOUNT,
+                "-" + format.format(receiptDTO.getFreeAmount())));
+    }
+
+    private static void printTotalPrice(ReceiptDTO receiptDTO) {
+        System.out.printf(OutputMessage.OUTPUT_RECEIPT_PRODUCT_MESSAGE.format(
+                OutputMessage.TOTAL_PRICE,
+                receiptDTO.getTotalQuantity(),
+                format.format(receiptDTO.getTotalAmount())));
+    }
+
+    private static void printFreeItems(ReceiptDTO receiptDTO) {
+        System.out.println(OutputMessage.OUTPUT_RECEIPT_MIDDLE_MESSAGE);
+        for (PurchaseDTO purchaseDTO : receiptDTO.free()) {
+            System.out.printf(OutputMessage.OUTPUT_RECEIPT_FREE_MESSAGE.format(
+                    purchaseDTO.name(), purchaseDTO.quantity()));
+        }
+    }
+
+    private static void printPurchaseItems(ReceiptDTO receiptDTO) {
+        System.out.println(OutputMessage.OUTPUT_RECEIPT_TOP_MESSAGE);
+        for (PurchaseDTO purchaseDTO : receiptDTO.cart()) {
+            System.out.printf(OutputMessage.OUTPUT_RECEIPT_PRODUCT_MESSAGE.format(
+                    purchaseDTO.name(),
+                    purchaseDTO.quantity(),
+                    format.format(purchaseDTO.getTotalAmount()))
+            );
+        }
+    }
+
+    private static String getPrice(Product product) {
+        return format.format(product.getPrice()) + OutputMessage.CURRENCY_UNIT;
+    }
+
+    private static String getPromotion(Product product) {
+        String promotion = "";
+        if (product.getPromotion() != null) {
+            promotion = product.getPromotion().getName();
+        }
+        return promotion;
+    }
+
+    private static String getQuantity(Product product) {
+        String quantity = format.format(product.getStock()) + OutputMessage.QUANTITY_UNIT;
+        if (product.getStock() == 0) {
+            quantity = OutputMessage.QUANTITY_EMPTY.toString();
+        }
+        return quantity;
+    }
+}

리뷰: 개수가 0이어도 출력이 되던데, 0일때 출력에서 제외시키는 것도 좋을 것 같습니다.