ShippingMethod.calculate() is called before address is selected?

36 views
Skip to first unread message

Ben Mehlman

unread,
Jul 12, 2021, 6:25:53 PM7/12/21
to django-oscar

Hi, I'm setting up Oscar, and I'm implementing a custom shipping method.
My shipping method uses a REST API to contact a server that calculates actual UPS shipping charges when given the contents of the shipment.

I forked the shipping app, implemented the Repository and Method, and implemented the calculate method of my Method class.. that all works!

Here's the problem:

When I put some items in the cart, and then view cart. Oscar calls Repository.get_available_shipping_methods(), however, the shipping_addr argument to that method is None, which makes sense, because the customer has not be asked for a shipping address yet.

So I return a tuple containing an instance of my shipping method.

Oscar then calls MyMethod.calculate(basket) on that instance, but I can't calculate anything, because there's no address yet.  I am expected to return a Price, but I can't...

So, I tried returning None.. that causes an exception.
Then I tried, in get_available_shipping_method, returning an empty tuple if no shipping_addr is supplied.  That's not ok either, Oscar tells me to implement some shipping methods...

So.. just for testing, I tried having calculate() return a zero price if there's no address supplied.  Sure enough, my cart displays, showing my shipping method with a zero price.

Then I can go on to the next page where it asks the customer to select an address.  I select one, and sure enough, get_available_shipping_methodsis called again, this time with the shipping_addr, and I can successfully calculate the shipping rate.

(Then, the payment page is displayed.. which for me is not implemented yet, because we use authorize.net.. I'll have to write that).

Can anyone tell me, what's the correct way to do this?  I want to delay the shipping calculation until after the customer has selected or entered the address.  I don't want to calculate or display a shipping price in the cart at all.

Thanks
Ben




solari...@gmail.com

unread,
Jul 13, 2021, 11:35:22 PM7/13/21
to django-oscar
Hi Ben,

As you've discovered, Oscar attempts to determine a shipping method for use on the basket view - before address information is provided. This is intended to provide the customer with indicative shipping cost before they check out.  I think the easiest way to get around this is to override the `basket_totals.html` template and change the `{% block shipping_total %}` block, to hide it completely if the basket is still editable, i.e.,

{% block shipping_total %}
    {% if not editable %}
        {{ block.super }}
    {% endif %}
{% endblock %}

You still need to return at least one shipping method in your repository class - but you can just return a dummy one when no shipping_addr is set (e.g., Free), since it will never be displayed.

Side note: Oscar's assumptions here are not ideal. I think it would be nice if it didn't require a shipping method for the BasketView. If you have time/inclination to make a PR to improve this then it would be nice to improve things. I don't think it would be a big change - just requires changing Repository.get_default_shipping_method not to raise an exception, and adding conditional logic to ensure shipping costs are not displayed if no default was found.

Cheers,

Samir

Ben Mehlman

unread,
Jul 14, 2021, 6:01:57 PM7/14/21
to django-oscar
Hi Samir, thank you for the help.  Sure I would not mind at all making a patch.

I'm working on my payment method code right now but when I go back to the shipping I'll take that approach and send the PR.  Hope to be able to add my payment method code to the project as well, once it's working nicely.

The other problem I need to solve with the Shipping Method is how to inform the customer if (when) shipping can't be calculated due to a temporary issue, missing data or etc.  So, from my ShippingMethod I need to be able to inform the customer of what happened.  I think the way to do that would be to have an exception class for that type of error that could be raised from within the ShippingMethod code, and result in an error message being added to the context and the current page redisplayed (whether for the user to fix their input if it's a user error (invalid postal code etc), or for them to just try re-submitting if it was a system problem).  What do you think of that?

Thanks
Ben

solari...@gmail.com

unread,
Jul 15, 2021, 12:32:43 AM7/15/21
to django-oscar
The other problem I need to solve with the Shipping Method is how to inform the customer if (when) shipping can't be calculated due to a temporary issue, missing data or etc.  So, from my ShippingMethod I need to be able to inform the customer of what happened.  I think the way to do that would be to have an exception class for that type of error that could be raised from within the ShippingMethod code, and result in an error message being added to the context and the current page redisplayed (whether for the user to fix their input if it's a user error (invalid postal code etc), or for them to just try re-submitting if it was a system problem).  What do you think of that?

Sounds fine to me - one approach you can look at is hooking in the pre_conditions check on the relevant step, to redirect back to the right place if certain conditions are not met.
Reply all
Reply to author
Forward
0 new messages