diff --git a/paypal/express/facade.py b/paypal/express/facade.py index a571b4f1..0a2acb11 100644 --- a/paypal/express/facade.py +++ b/paypal/express/facade.py @@ -34,8 +34,12 @@ 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')) - cancel_url = '%s://%s%s' % (scheme, host, reverse('paypal-cancel-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})) # 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..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/', 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 4a5406b1..f599519a 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') @@ -116,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): @@ -142,7 +165,18 @@ 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 HttpResponseRedirect(reverse('basket:summary')) + + return self.submit(basket, order_kwargs=order_kwargs) def fetch_paypal_data(self, payer_id, token): self.payer_id = payer_id @@ -156,9 +190,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 %}