I am writing a simple application that prints out the details of a receipt containing items purchased by a customer. The receipt should list the number of items, the name, the final price (including taxes) of each of the item purchased, plus two extra lines showing the total amount of the receipt and the total amount of taxes.Taxes are calculated with a rate of 17.5%, rounding the result to the upper 0.05. Please note that medical products are exempt and an additional 1.25 fixed amount is added as an extra tax on CDs.Examples:Input: One book at 29.49  One music CD at 15.99  List one chocolate snack at 0.75Output: 1 book: 34.69  1 music CD: 20.04  1 chocolate snack: 0.90Sales Taxes: 9.40  Total: 55.63Input: One bottle of wine at 20.99One box of tooth ache pills at 4.15One box of pins at 11.25One music CD at 14.99Output: 1 bottle of wine: 24.691 box of headache pills: 4.151 box of pins: 13.251 music CD: 18.89Sales Taxes: 9.60Total: 60.98Basket class:public class Basket {    private final ProductService productService;    private final TaxService taxService;    private final Map<String, LineItem> items;    public Basket(ProductService productService, TaxService taxService) {        this.items = new HashMap<>();        this.productService = productService;        this.taxService = taxService;    }    public void addItems(String... names) throws ProductNotFoundException {        for(String name : names) {            if (items.containsKey(name.toLowerCase())) {                updateExistingLineItem(name);            } else {                addNewLineItem(name);            }        }    }    private void updateExistingLineItem(String name) throws ProductNotFoundException {        Cost cost = calculateCost(name);        items.get(name.toLowerCase()).update(cost);    }    private void addNewLineItem(String name) throws ProductNotFoundException {        Cost cost = calculateCost(name);        LineItem lineItem = new LineItem(name, cost);        items.put(name.toLowerCase(), lineItem);    }    private Cost calculateCost(String name) throws ProductNotFoundException {        BigDecimal gross = productService.price(name);        BigDecimal tax = taxService.calculateTax(name);        return new Cost(gross, tax);    }    public BigDecimal getTotalPrice() {        return items.values()                .stream().map(LineItem::getNetPrice)                .reduce(ZERO, BigDecimal::add);    }    public BigDecimal getSalesTax() {        return items.values()                .stream().map(LineItem::getTax)                .reduce(ZERO, BigDecimal::add);    }    @Override    public String toString() {        String formattedItems = items.values().toString()                .replace(,, )                .replace([, )                .replace(], )                .trim();        return formattedItems + \nSales Taxes:  + getSalesTax() + \nTotal:  + getTotalPrice() + \n;    }}Cost class:public class Cost {    private final BigDecimal gross;    private final BigDecimal tax;    public Cost(BigDecimal gross, BigDecimal tax) {        this.gross = gross;        this.tax = tax;    }    public Cost add(Cost cost) {        return new Cost(this.gross.add(cost.gross), this.tax.add(cost.tax));    }    public BigDecimal getGross() {        return gross;    }    public BigDecimal getTax() {        return tax;    }}LineItem class:public class LineItem {    private final String name;    private Integer quantity = 0;    private Cost cost;    public LineItem(String name, Integer quantity, Cost cost) {        this.name = name;        this.quantity = quantity;        this.cost = cost;    }    public LineItem(String name, Cost cost) {        this(name, 1, cost);    }    public void update(Cost cost) {        this.cost = this.cost.add(cost);        this.quantity++;    }    public BigDecimal getTax() {        return this.cost.getTax();    }    public BigDecimal getNetPrice() {        return cost.getGross().add(getTax());    }    @Override    public String toString() {        StringJoiner joiner = new StringJoiner( );        joiner.add(quantity.toString());        joiner.add(name);        joiner.add(:);        joiner.add(getNetPrice().toString());        return \n + joiner.toString();    }}Product class:public class Product {    private String name;    private BigDecimal price;    private List<TaxBand> applicableTaxes;    public Product(String name, String price, TaxBand... applicableTaxes) {        this.name = name;        this.price = new BigDecimal(price);        this.applicableTaxes = asList(applicableTaxes);    }    public String getName() {        return name;    }    public BigDecimal getPrice() {        return price;    }    public List<TaxBand> getApplicableTaxes() {        return applicableTaxes;    }}ProductService class:public class ProductService {    private ProductRepository productRepository;    public ProductService(ProductRepository productRepository, Product... items) {        this.productRepository = productRepository;        Arrays.stream(items).forEach(item -> this.productRepository.add(item));    }    public Product getProductByName(String name) throws ProductNotFoundException {        return productRepository.findByProductName(name);    }    public BigDecimal price(String name) throws ProductNotFoundException {        return productRepository.price(name);    }}MapProductRepository class:public class MapProductRepository implements ProductRepository {    private final Map<String, Product> products = new HashMap<>();    @Override    public Product findByProductName(String name) throws ProductNotFoundException {        Product product = products.get(name.toLowerCase());        if(product == null) {            throw new ProductNotFoundException(name +  not supported);        }        return product;    }    @Override    public BigDecimal price(String name) throws ProductNotFoundException{        return findByProductName(name.toLowerCase()).getPrice();    }    @Override    public void add(Product product) {        products.put(product.getName().toLowerCase(), product);    }}ProductRepository interface:public interface ProductRepository {    Product findByProductName(String name) throws ProductNotFoundException;    BigDecimal price(String name) throws ProductNotFoundException;    void add(Product product);}public class CompactDiskTax implements Tax {    public static final BigDecimal TAX = new BigDecimal(1.25);    @Override    public BigDecimal calculate(Product product) {        if(!product.getApplicableTaxes().contains(CD)){            return ZERO;        }        return TAX;    }}MiscTax class:public class MiscTax implements Tax {    public static final BigDecimal ROUND_TO = new BigDecimal(0.05);    public static final BigDecimal TAX_RATE = new BigDecimal(17.5);    public BigDecimal calculate(Product product) {        if(!product.getApplicableTaxes().contains(TaxBand.MISC)){            return ZERO;        }        return percentage(product.getPrice(), TAX_RATE);    }    private BigDecimal percentage(BigDecimal base, BigDecimal percentage){        return round(base.multiply(percentage).divide(new BigDecimal(100)));    }    private BigDecimal round(BigDecimal value) {        BigDecimal divided = value.divide(ROUND_TO, 0, UP);        return divided.multiply(ROUND_TO);    }}Tax interface:public interface Tax {    BigDecimal calculate(Product product);}public enum TaxBand {    MEDICAL, CD, MISC}public class TaxService {    private ProductService productService;    private List<Tax> taxes;    public TaxService(ProductService productService, Tax... taxes) {        this.productService = productService;        this.taxes = Arrays.asList(taxes);    }    public BigDecimal calculateTax(String productName) throws ProductNotFoundException {        Product product = productService.getProductByName(productName);        return taxes.stream()                .map(tax -> tax.calculate(product))                .reduce(BigDecimal.ZERO, BigDecimal::add);    }}
Shopping basket with discounts and tax
java;e commerce