diefenbach / django-lfs

An online-shop based on Django
http://www.getlfs.com
BSD 3-Clause "New" or "Revised" License
622 stars 222 forks source link

re-assign customer.selected_shipping_method in one_page_checkout view #67

Open baffolobill opened 11 years ago

baffolobill commented 11 years ago

Let's assume a shipping method depends on some criteria (country, city, etc.). On the cart page customer has selected "method1", but on the checkout page some criteria has been changed (for example, country) and updated list of the shipping methods was loaded. The updated list doesn't contain the "method1" and none of the methods is selected. The customer doesn't select the new one method and press "submit order" button. There are two major bugs in this case: 1) incorrect order total (order.price), because the old shipping method and shipping cost will be used for calculations; 2) incorrect shipping method will be assigned to the order.

pigletto commented 11 years ago

Which version of LFS do you have? Seems to me that this works properly in the latest version from the github repository

baffolobill commented 11 years ago

I have a fork based on v0.7. It works fine for me too. But I found that in one_page_checkout there is no check whether the value is set for the fields shipping_method & payment_method. I've added to a form:

class OnePageCheckoutForm(forms.Form):
...
    shipping_method = forms.CharField(required=True, max_length=2)
    payment_method = forms.CharField(required=True, max_length=2)
...

    def __init__(self, *args, **kwargs):
        self.request = kwargs.pop('request', None)
        super(OnePageCheckoutForm, self).__init__(*args, **kwargs)
        ...
        customer = customer_utils.get_customer(self.request)
        if customer:
            # Now I'm sure that shipping/payment method is valid
            lfs.shipping.utils.update_to_valid_shipping_method(self.request, customer)
            lfs.payment.utils.update_to_valid_payment_method(self.request, customer)
            customer.save()
            ...
            self.fields['shipping_method'].initial = customer.selected_shipping_method_id
            self.fields['payment_method'].initial = customer.selected_payment_method_id

I'll try to show you, why that's the possible bug (look at the comments in the code)

# lfs/checkout/views.py
def one_page_checkout(request, template_name="lfs/checkout/one_page_checkout.html"):
    ...
    return render_to_response(template_name, RequestContext(request, {
       ...
        "shipping_inline": shipping_inline(request),
        "payment_inline": payment_inline(request, bank_account_form),
       ...
    }))

...
def payment_inline(request, form, template_name="lfs/checkout/payment_inline.html"):
    ...
    selected_payment_method = lfs.payment.utils.get_selected_payment_method(request)
# there is no guarantee that the <selected_payment_method> will be in the list
    valid_payment_methods = lfs.payment.utils.get_valid_payment_methods(request)
    ...

def shipping_inline(request, template_name="lfs/checkout/shipping_inline.html"):
    ...
    selected_shipping_method = lfs.shipping.utils.get_selected_shipping_method(request)
# there is no guarantee that the <selected_shipping_method> will be in the list
    shipping_methods = lfs.shipping.utils.get_valid_shipping_methods(request)
    ...

# lfs/payment/utils.py
def get_selected_payment_method(request):
    customer = customer_utils.get_customer(request)
    if customer and customer.selected_payment_method:
# THIS VALUE MAY BE INVALID
# and as a result not selected in the rendered html.
# That's why I've made `payment_method` required in the OnePageCheckoutForm.
        return customer.selected_payment_method
    else:
        return get_default_payment_method(request)

# lfs/shipping/utils.py
def get_selected_shipping_method(request):
    customer = customer_utils.get_customer(request)
    if customer and customer.selected_shipping_method:
# The same situation is here.
        return customer.selected_shipping_method
    else:
        return get_default_shipping_method(request)
<!-- lfs/checkout/payment_inline.html -->
{% for payment_method in payment_methods %}
  ...
  <input type="radio"
        name="payment_method"
<!-- there is no guarantee that the `selected_payment_method` will be in the list -->
        {% ifequal payment_method.id selected_payment_method.id  %}checked="checked"{% endifequal %}
    ...
{% endfor %}