From 534fd8c55b9b1b3081eca2b8b009d33a2f4b35df Mon Sep 17 00:00:00 2001 From: David Winterbottom Date: Thu, 13 Jun 2013 12:00:57 +0100 Subject: [PATCH 1/3] Freeze basket on redirect and thaw on cancel --- paypal/express/facade.py | 4 +++- paypal/express/urls.py | 2 +- paypal/express/views.py | 14 +++++++++++++- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/paypal/express/facade.py b/paypal/express/facade.py index a571b4f1..d94c895c 100644 --- a/paypal/express/facade.py +++ b/paypal/express/facade.py @@ -35,7 +35,9 @@ def get_paypal_url(basket, shipping_methods, user=None, shipping_address=None, if host is None: host = Site.objects.get_current().domain return_url = '%s://%s%s' % (scheme, host, reverse('paypal-success-response')) - cancel_url = '%s://%s%s' % (scheme, host, reverse('paypal-cancel-response')) + cancel_url = '%s://%s%s' % ( + scheme, host, reverse('paypal-cancel-response', kwargs={ + 'basket_id': basket.id})) # URL for updating shipping methods - we only use this if we have a set of # shipping methods to choose between. diff --git a/paypal/express/urls.py b/paypal/express/urls.py index ad936444..66629d42 100644 --- a/paypal/express/urls.py +++ b/paypal/express/urls.py @@ -9,7 +9,7 @@ url(r'^redirect/', views.RedirectView.as_view(), name='paypal-redirect'), url(r'^preview/', views.SuccessResponseView.as_view(preview=True), name='paypal-success-response'), - url(r'^cancel/', views.CancelResponseView.as_view(), + url(r'^cancel/(?P\d+)/', views.CancelResponseView.as_view(), name='paypal-cancel-response'), url(r'^place-order/', views.SuccessResponseView.as_view(), name='paypal-place-order'), diff --git a/paypal/express/views.py b/paypal/express/views.py index 4a5406b1..853a6f9d 100644 --- a/paypal/express/views.py +++ b/paypal/express/views.py @@ -37,7 +37,7 @@ class RedirectView(CheckoutSessionMixin, RedirectView): def get_redirect_url(self, **kwargs): try: - return self._get_redirect_url(**kwargs) + url = self._get_redirect_url(**kwargs) except PayPalError: messages.error(self.request, _("An error occurred communicating with PayPal")) if self.as_payment_method: @@ -45,6 +45,12 @@ def get_redirect_url(self, **kwargs): else: url = reverse('basket:summary') return url + else: + # Transaction successfully registered with PayPal. Now freeze the + # basket so it can't be edited while the customer is on the PayPal + # site. + self.request.basket.freeze() + return url def _get_redirect_url(self, **kwargs): basket = self.request.basket @@ -88,6 +94,12 @@ def _get_redirect_url(self, **kwargs): class CancelResponseView(RedirectView): permanent = False + def get(self, request, *args, **kwargs): + basket = get_object_or_404(Basket, id=kwargs['basket_id'], + status=Basket.FROZEN) + basket.thaw() + return super(CancelResponseView, self).get(request, *args, **kwargs) + def get_redirect_url(self, **kwargs): messages.error(self.request, _("PayPal transaction cancelled")) return reverse('basket:summary') From 282454355589042fe79738634cb841e3dcfc764a Mon Sep 17 00:00:00 2001 From: David Winterbottom Date: Thu, 13 Jun 2013 12:30:11 +0100 Subject: [PATCH 2/3] Submit the frozen basket within success view This involves some trickyness with the templates as the default preview.html wants to render the open basket (which is injected into the template context via the basket middleware). We duplicate some of the basket template so we can render an alternative basket (the frozen one). --- paypal/express/facade.py | 4 +- paypal/express/urls.py | 7 +-- paypal/express/views.py | 27 +++++++++- paypal/templates/paypal/express/preview.html | 54 +++++++++++++++++++- 4 files changed, 86 insertions(+), 6 deletions(-) diff --git a/paypal/express/facade.py b/paypal/express/facade.py index d94c895c..0a2acb11 100644 --- a/paypal/express/facade.py +++ b/paypal/express/facade.py @@ -34,7 +34,9 @@ def get_paypal_url(basket, shipping_methods, user=None, shipping_address=None, currency = getattr(settings, 'PAYPAL_CURRENCY', 'GBP') if host is None: host = Site.objects.get_current().domain - return_url = '%s://%s%s' % (scheme, host, reverse('paypal-success-response')) + return_url = '%s://%s%s' % ( + scheme, host, reverse('paypal-success-response', kwargs={ + 'basket_id': basket.id})) cancel_url = '%s://%s%s' % ( scheme, host, reverse('paypal-cancel-response', kwargs={ 'basket_id': basket.id})) diff --git a/paypal/express/urls.py b/paypal/express/urls.py index 66629d42..d833941d 100644 --- a/paypal/express/urls.py +++ b/paypal/express/urls.py @@ -7,11 +7,12 @@ urlpatterns = patterns('', # Views for normal flow that starts on the basket page url(r'^redirect/', views.RedirectView.as_view(), name='paypal-redirect'), - url(r'^preview/', views.SuccessResponseView.as_view(preview=True), + url(r'^preview/(?P\d+)/$', + views.SuccessResponseView.as_view(preview=True), name='paypal-success-response'), - url(r'^cancel/(?P\d+)/', views.CancelResponseView.as_view(), + url(r'^cancel/(?P\d+)/$', views.CancelResponseView.as_view(), name='paypal-cancel-response'), - url(r'^place-order/', views.SuccessResponseView.as_view(), + url(r'^place-order/(?P\d+)/$', views.SuccessResponseView.as_view(), name='paypal-place-order'), # Callback for getting shipping options for a specific basket url(r'^shipping-options/(?P\d+)/', diff --git a/paypal/express/views.py b/paypal/express/views.py index 853a6f9d..8b756399 100644 --- a/paypal/express/views.py +++ b/paypal/express/views.py @@ -128,6 +128,17 @@ def get(self, request, *args, **kwargs): except PayPalError: messages.error(self.request, _("A problem occurred communicating with PayPal - please try again later")) return HttpResponseRedirect(reverse('basket:summary')) + + # Lookup the frozen basket that this txn corresponds to + try: + self.basket = Basket.objects.get(id=kwargs['basket_id'], + status=Basket.FROZEN) + except Basket.DoesNotExist: + messages.error( + self.request, + _("No basket was found that corresponds to your " + "PayPal transaction")) + return super(SuccessResponseView, self).get(request, *args, **kwargs) def post(self, request, *args, **kwargs): @@ -154,7 +165,17 @@ def post(self, request, *args, **kwargs): # Pass the user email so it can be stored with the order order_kwargs = {'guest_email': self.txn.value('EMAIL')} - return self.submit(request.basket, order_kwargs=order_kwargs) + # Lookup the frozen basket that this txn corresponds to + try: + basket = Basket.objects.get(id=kwargs['basket_id'], + status=Basket.FROZEN) + except Basket.DoesNotExist: + messages.error( + self.request, + _("No basket was found that corresponds to your " + "PayPal transaction")) + + return self.submit(basket, order_kwargs=order_kwargs) def fetch_paypal_data(self, payer_id, token): self.payer_id = payer_id @@ -168,9 +189,13 @@ def get_error_response(self): def get_context_data(self, **kwargs): ctx = super(SuccessResponseView, self).get_context_data(**kwargs) + if not hasattr(self, 'payer_id'): return ctx + + # This context generation only runs when in preview mode ctx.update({ + 'frozen_basket': self.basket, 'payer_id': self.payer_id, 'token': self.token, 'paypal_user_email': self.txn.value('EMAIL'), diff --git a/paypal/templates/paypal/express/preview.html b/paypal/templates/paypal/express/preview.html index 88d8b0e5..09f62ad9 100644 --- a/paypal/templates/paypal/express/preview.html +++ b/paypal/templates/paypal/express/preview.html @@ -1,6 +1,7 @@ {% extends "checkout/preview.html" %} {% load currency_filters %} {% load i18n %} +{% load thumbnail %} {# Null out the actions as they can't be used here #} {% block shipping_address_actions %}{% endblock %} @@ -23,9 +24,60 @@

{% trans "PayPal" %}

{% endblock %} +{% comment %} + Regrettably, we need to duplicate the order_contents block from Oscar's core checkout/checkout/html template. + This is because we want to render the frozen basket details here not the current open basket. As of Oscar 0.5.1, + the basket middlware injects the current open basket into the template context which stops us from being able to + automatically render the frozen basket using the default template. This will be fixed in 0.6, after which we can + remove this block +{% endcomment %} +{% block order_contents %} +
+

{% trans "Order contents" %}

+
+
+
+

{% trans "Items in basket" %}

+

{% trans "Quantity" %}

+

{% trans "Price" %}

+
+
+ {% for line in frozen_basket.all_lines %} +
+
+
+
+ {% with image=line.product.primary_image %} + {% thumbnail image.original "200x200" upscale=False as thumb %} + {{ product.get_title }} + {% endthumbnail %} + {% endwith %} +
+

{{ line.description }}

+ {{ line.product.stockrecord.availability }} +
+
+ {{ line.quantity }} +
+
+

{{ line.line_price_incl_tax|currency }}

+
+
+
+ {% endfor %} + +
+
 
+
+ {% include 'basket/partials/basket_totals.html' with basket=frozen_basket %} +
+
+ +{% endblock order_contents %} + {% block place_order %}

{% trans "Please review the information above, then click 'Place Order'" %}

-
+ {% csrf_token %} From 5ed4f6cba96b401697170cbf89770d22ec14e48f Mon Sep 17 00:00:00 2001 From: David Winterbottom Date: Fri, 14 Jun 2013 10:31:12 +0100 Subject: [PATCH 3/3] Add redirect for when basket can't be look up --- paypal/express/views.py | 1 + 1 file changed, 1 insertion(+) diff --git a/paypal/express/views.py b/paypal/express/views.py index 8b756399..f599519a 100644 --- a/paypal/express/views.py +++ b/paypal/express/views.py @@ -174,6 +174,7 @@ def post(self, request, *args, **kwargs): self.request, _("No basket was found that corresponds to your " "PayPal transaction")) + return HttpResponseRedirect(reverse('basket:summary')) return self.submit(basket, order_kwargs=order_kwargs)