Skip to content

Commit

Permalink
Merge pull request #128 from django-oscar/check-shipping-options
Browse files Browse the repository at this point in the history
Added api to check shipping options when shipping address is known.
  • Loading branch information
Martijn Jacobs committed Aug 17, 2018
2 parents 70cef33 + a737c0f commit b90a0f0
Show file tree
Hide file tree
Showing 4 changed files with 160 additions and 16 deletions.
51 changes: 49 additions & 2 deletions docs/source/usage/communicate_with_the_api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,12 @@ Now we will delete this line, it will return a 204 when it's successful:
Place an order (checkout)
-------------------------

When your basket is filled an you want to proceed to checkout you can do a single call with all information needed. Note that we are doing an anonymous checkout here, so we need to set the `guest_email` field. (Make sure that ``OSCAR_ALLOW_ANON_CHECKOUT`` is set to ``True`` in your ``settings.py``). If you don't support anonymous checkouts you will have to login the user first (see login example below).
When your basket is filled an you want to proceed to checkout you can do a
single call with all information needed. Note that we are doing an anonymous
checkout here, so we need to set the `guest_email` field. (Make sure that
``OSCAR_ALLOW_ANON_CHECKOUT`` is set to ``True`` in your ``settings.py``).
If you don't support anonymous checkouts you will have to login the user first
(see login example below).

.. code-block:: python
Expand Down Expand Up @@ -236,12 +241,32 @@ When your basket is filled an you want to proceed to checkout you can do a singl
# we need the country url in the shipping address
country_url = countries[0]['url']
# we need to check the available shipping options
response = session.get('http://localhost:8000/api/basket/shipping-methods/')
shipping_methods = response.json()
print(shipping_methods)
[
{
"code": "free-shipping",
"name": "Free shipping",
"price": {
"currency": "EUR",
"excl_tax": "0.00",
"incl_tax": "0.00",
"tax": "0.00"
}
}
]
# pick one
shipping_method = shipping_methods[0]
# let's fill out the request data
data = {
"basket": basket_data['url'],
"guest_email": guest_email,
"total": basket_data['total_incl_tax'],
"shipping_method_code": "no-shipping-required",
"shipping_method_code": shipping_method['code'],
# the shipping charge is optional, but we leave it here for example purposes
"shipping_charge": {
"currency": basket_data['currency'],
Expand Down Expand Up @@ -330,6 +355,28 @@ When your basket is filled an you want to proceed to checkout you can do a singl
.. note::
After you placed an order with the api, the basket is frozen. Oscar API has checks for this in the checkout view and won't let you checkout the same (or any frozen) basket again. At this stage an order is submitted in Oscar and you will have to implement the following steps regarding payment yourself. See the ``payment_url`` field above in the response. You can also use the regular Oscar checkout views if you like, take a look at the :ref:`mixed-usage-label` section.

.. note::
If your shipping methods depend in any way on the shipping address, you can
also POST to the shipping_method api. Just post the shipping details in
the same format as accepted by the checkout api::
{
"country": "http://localhost:8000/api/countries/NL/",
"first_name": "Henk",
"id": 3,
"last_name": "Van den Heuvel",
"line1": "Roemerlaan 44",
"line2": "",
"line3": "",
"line4": "Kroekingen",
"notes": "",
"phone_number": "+31 26 370 4887",
"postcode": "7777KK",
"search_text": "Henk Van den Heuvel Roemerlaan 44 Kroekingen Gerendrecht 7777KK Kingdom of the Netherlands",
"state": "Gerendrecht",
"title": "Mr"
}

.. note::
In the checkout view of Oscar, the function ``handle_successful_order`` is called after placing an order. This sends the order confirmation message, flushes your session and sends the ``post_checkout`` signal. The Oscar API checkout view is not calling this method by design. If you would like to send a confirmation message (or other stuff you need to do) after placing an order you can subscribe to the ``oscarapi_post_checkout`` signal, see :doc:`/usage/signals`.

Expand Down
56 changes: 56 additions & 0 deletions oscarapi/tests/testcheckout.py
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,62 @@ def test_checkout_permissions(self):
self.assertEqual(response.status_code, 401)
self.assertEqual(response.data, "Unauthorized")

def test_shipping_methods(self):
"""Test if shipping methods can be fetched for baskets."""
self.login(username='nobody', password='nobody')
response = self.get('api-basket')
self.assertTrue(response.status_code, 200)
basket = response.data
basket_url = basket.get('url')
basket_id = basket.get('id')

request = {
"country": "http://127.0.0.1:8000/api/countries/NL/",
"first_name": "Henk",
"last_name": "Van den Heuvel",
"line1": "Roemerlaan 44",
"line2": "",
"line3": "",
"line4": "Kroekingen",
"notes": "Niet STUK MAKEN OK!!!!",
"phone_number": "+31 26 370 4887",
"postcode": "7777KK",
"state": "Gerendrecht",
"title": "Mr"
}
self.response = self.post('api-basket-shipping-methods', **request)
self.response.assertStatusEqual(200)
self.assertEqual(len(self.response), 1)
self.assertDictEqual(self.response[0], {
'code': 'no-shipping-required',
'name': 'No shipping required',
'price': {
'currency': None,
'excl_tax': '0.00',
'incl_tax': '0.00',
'tax': '0.00'
}
})
response = self.post(
'api-basket-add-product',
url="http://testserver/api/products/1/", quantity=5)
self.assertEqual(response.status_code, 200)

self.response = self.post('api-basket-shipping-methods', **request)
self.response.assertStatusEqual(200)
self.assertEqual(len(self.response), 1)
self.assertDictEqual(self.response[0], {
'code': 'free-shipping',
'name': 'Free shipping',
'price': {
'currency': 'EUR',
'excl_tax': '0.00',
'incl_tax': '0.00',
'tax': '0.00'
}
})


@unittest.skip('Please add implementation')
def test_cart_immutable_after_checkout(self):
"Prove that the cart can not be changed with the webservice by users in any way after checkout has been made."
Expand Down
2 changes: 1 addition & 1 deletion oscarapi/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
url(r'^basket/$', views.BasketView.as_view(), name='api-basket'),
url(r'^basket/add-product/$', views.AddProductView.as_view(), name='api-basket-add-product'),
url(r'^basket/add-voucher/$', views.AddVoucherView.as_view(), name='api-basket-add-voucher'),
url(r'^basket/shipping-methods/$', views.shipping_methods, name='api-basket-shipping-methods'),
url(r'^basket/shipping-methods/$', views.ShippingMethodView.as_view(), name='api-basket-shipping-methods'),
url(r'^baskets/$', views.BasketList.as_view(), name='basket-list'),
url(r'^baskets/(?P<pk>[0-9]+)/$', views.BasketDetail.as_view(), name='basket-detail'),
url(r'^baskets/(?P<pk>[0-9]+)/lines/$', views.LineList.as_view(), name='basket-lines-list'),
Expand Down
67 changes: 54 additions & 13 deletions oscarapi/views/basket.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,12 @@
from oscarapi.views.utils import BasketPermissionMixin

__all__ = ('BasketView', 'LineList', 'LineDetail', 'AddProductView',
'BasketLineDetail', 'AddVoucherView', 'shipping_methods')
'BasketLineDetail', 'AddVoucherView', 'ShippingMethodView')

Basket = get_model('basket', 'Basket')
Line = get_model('basket', 'Line')
Repository = get_class('shipping.repository', 'Repository')
ShippingAddress = get_model('order', 'ShippingAddress')


class BasketView(APIView):
Expand Down Expand Up @@ -155,22 +156,62 @@ def post(self, request, format=None):
return Response(v_ser.errors, status=status.HTTP_406_NOT_ACCEPTABLE)


@api_view(('GET',))
def shipping_methods(request, format=None):
class ShippingMethodView(APIView):
"""
Get the available shipping methods and their cost for this order.
GET:
A list of shipping method details and the prices.
Retrieve shipping methods available to the user, basket, request combination
POST(shipping_address):
{
"country": "http://127.0.0.1:8000/oscarapi/countries/NL/",
"first_name": "Henk",
"last_name": "Van den Heuvel",
"line1": "Roemerlaan 44",
"line2": "",
"line3": "",
"line4": "Kroekingen",
"notes": "Niet STUK MAKEN OK!!!!",
"phone_number": "+31 26 370 4887",
"postcode": "7777KK",
"state": "Gerendrecht",
"title": "Mr"
}
Post a shipping_address if your shipping methods are dependent on the
address.
"""
basket = operations.get_basket(request)
shiping_methods = Repository().get_shipping_methods(
basket=basket, user=request.user,
request=request)
ser = serializers.ShippingMethodSerializer(
shiping_methods, many=True, context={'basket': basket})
return Response(ser.data)
serializer_class = serializers.ShippingAddressSerializer

def _get(self, request, shipping_address=None, format=None):
basket = operations.get_basket(request)
shiping_methods = Repository().get_shipping_methods(
basket=basket, user=request.user, shipping_addr=shipping_address,
request=request)
ser = serializers.ShippingMethodSerializer(
shiping_methods, many=True, context={'basket': basket})
return Response(ser.data)

def get(self, request, format=None):
"""
Get the available shipping methods and their cost for this order.
GET:
A list of shipping method details and the prices.
"""
return self._get(request, format=format)

def post(self, request, format=None):
s_ser = self.serializer_class(
data=request.data, context={'request': request})
if s_ser.is_valid():
shipping_address = ShippingAddress(**s_ser.validated_data)
return self._get(
request,
format=format,
shipping_address=shipping_address
)

return Response(s_ser.errors, status=status.HTTP_406_NOT_ACCEPTABLE)

class LineList(BasketPermissionMixin, generics.ListCreateAPIView):
"""
Expand Down

0 comments on commit b90a0f0

Please sign in to comment.