I think it is worse than that. To begin with, it makes no sense (to me) for an order to have a single product by definition. This is what I think you quickly spotted.
I understand that the author does not have an "Order" because that is supposed to be somewhat definitive (a domain object stored in the database). If that is the case, a much better approach would be to have a "PreOrder" object, that is basically the same but lives in the session scope.
This PreOrder object (or ShoppingCart, or whatever) should basically "extend" (share everything with) the Order object, inheriting all its capabilities _except that of being persistent_. This includes the logic for computing prices based on discount codes/rates/delivery chargers/whatever that you normally apply to a regular Order.
Now, if you want to check if a product would be free, you just add a helper method "free?(product)" to the PreOrder. That method would just get the "price()" of the order, add the product to the PreOrder (itself), check if the new "price()" is higher than before, remove the product, return the result of the price increase check.
At this point you have accounted for any possible interaction between product quantities, discount codes and rates, user credit, delivery costs and whatever. No code duplication involved _at all_.
Moreover, I would say that it is actually _less_ complex (as in difficult to understand) to build such "PreOrder"s than to add random methods to your domain objects...