One-page checkout:
- Put shipping customer profile form and shipping service selection on same page.
- Shipping options are updated with ajax, when the address changes.
In this scenario, it is possible to submit before the ajax fires or finishes, see #2043597: Disable 'continue' button on checkout form during ajax shipping recalculation (this was closed as "outdated", even though it still applies)
Problem
When doing this, the changes in the shipping address will not be reflected in the final shipping cost.
Example:
- Type an address with e.g. City = Aachen.
- Let the shipping calculation finish.
- Choose the shipping service, which is based on distance to Aachen.
- Change the city, e.g. City = Berlin, but keep the focus on this field, to prevent the onchange event.
- Hit "Next step" to submit.
On the "review order" page, we now see an order with:
- shipping address with city = Berlin
- shipping cost based on city = Aachen
In this simple example, there will be a geocoding conflict because the postal code can only fit to one of those places.
In my own custom shipping service, the rate would be set to 99 EUR if the destination cannot be geocoded.
I chose this example because it is easy to reproduce.
We could change mulitple fields of the address at once (e.g. with browser autofill) and then quickly submit, before ajax finishes.
In this case there can be valid geocoded locations for old and new address, and the shipping distance will be based on the old address.
Even if we were to implement #2043597: Disable 'continue' button on checkout form during ajax shipping recalculation to freeze the submit button, this would not offer full protection:
- As before: if the form is submitted before the onchange event fires from changing city or postal code, the submit button won't be disabled yet.
- Someone could maliciously circumvent the client-side freeze, and submit the form anyway.
Also, the form would become unusable if the ajax request is somehow interrupted.
Solution, step 1
The shipping cost needs to be recalculated on submit.
How can we do this?
Option 1:
Recalculate the shipping cost of the chosen service on commerce_shipping_rate_apply(), instead of using the value from $order->shipping_rates.
.
Option 2: (?)
Implement hook_commerce_customer_profile_update().
Whenever the shipping profile is updated, also recalculate the shipping costs, set order->shipping_rates, and save the order?
Not really possible, I think, because the rates are not saved with the order.
Option 3:
Recalculate shipping rates on shipping pane validation.
These options are not fully thought-through, but we have to start somewhere.
Problem with this
The solutions above will make sure that the final shipping cost is calculated based on the final customer address.
However, the user has chosen the shipping method based on the old price.
Example:
User enters address "Aachen", shipping cost is 30 EUR. User chooses shipping method.
User changes address to "Berlin", shipping cost still shows 30 EUR until the ajax update.
User submits, before ajax triggers or finishes.
On "review order", the shipping cost is now 60 EUR to Berlin, instead of the 30 EUR to Aachen.
This is the correct behavior, but we need to tell the user about it.
It is displayed in the review page, but people can easily overlook that, assuming that it is the same as before.
Solution, step 2
Tell the user that the shipping cost has been updated.
Option 1:
Call drupal_set_message() to tell about shipping cost update.
Option 2:
Add a validation handler for the shipping method selection, which
- recalculates shipping costs
- fails, if the calculation produces a different result than before.
This would show force the user to submit the same page again.
Performance?
Calculating a shipping cost can take a while, if geocoding or other external API requests are involved.
Due to the way that shipping services work, it should be up to them to cache the result, and only recalculate if relevant input data has changed.
Comments