Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Basket needs to be frozen while customer is at PayPal's site #24

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions paypal/express/facade.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
7 changes: 4 additions & 3 deletions paypal/express/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -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<basket_id>\d+)/$',
views.SuccessResponseView.as_view(preview=True),
name='paypal-success-response'),
url(r'^cancel/', views.CancelResponseView.as_view(),
url(r'^cancel/(?P<basket_id>\d+)/$', views.CancelResponseView.as_view(),
name='paypal-cancel-response'),
url(r'^place-order/', views.SuccessResponseView.as_view(),
url(r'^place-order/(?P<basket_id>\d+)/$', views.SuccessResponseView.as_view(),
name='paypal-place-order'),
# Callback for getting shipping options for a specific basket
url(r'^shipping-options/(?P<basket_id>\d+)/',
Expand Down
42 changes: 40 additions & 2 deletions paypal/express/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,20 @@ 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:
url = reverse('checkout:payment-details')
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
Expand Down Expand Up @@ -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')
Expand Down Expand Up @@ -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):
Expand All @@ -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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basket is not available if the get fails, is it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well spotted.


def fetch_paypal_data(self, payer_id, token):
self.payer_id = payer_id
Expand All @@ -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'),
Expand Down
54 changes: 53 additions & 1 deletion paypal/templates/paypal/express/preview.html
Original file line number Diff line number Diff line change
@@ -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 %}
Expand All @@ -23,9 +24,60 @@ <h4>{% trans "PayPal" %}</h4>
</div>
{% 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 %}
<div class="sub-header">
<h2>{% trans "Order contents" %}</h2>
</div>
<div class="basket-title">
<div class="row-fluid">
<h4 class="span9">{% trans "Items in basket" %}</h4>
<h4 class="span1 align-center">{% trans "Quantity" %}</h4>
<h4 class="span2 align-right">{% trans "Price" %}</h4>
</div>
</div>
{% for line in frozen_basket.all_lines %}
<div class="basket-items">
<div class="row-fluid">
<div class="span9">
<div class="image_container">
{% with image=line.product.primary_image %}
{% thumbnail image.original "200x200" upscale=False as thumb %}
<a href="{{ form.instance.product.get_absolute_url }}"><img class="thumbnail" src="{{ thumb.url }}" alt="{{ product.get_title }}"></a>
{% endthumbnail %}
{% endwith %}
</div>
<h4><a href="{{ line.product.get_absolute_url }}">{{ line.description }}</a></h4>
<span class="availability {{ line.product.stockrecord.availability_code }}">{{ line.product.stockrecord.availability }}</span>
</div>
<div class="span1 align-center">
{{ line.quantity }}
</div>
<div class="span2 align-right">
<p class="price_color">{{ line.line_price_incl_tax|currency }}</p>
</div>
</div>
</div>
{% endfor %}

<div class="row-fluid">
<div class="span6">&nbsp;</div>
<div class="span6">
{% include 'basket/partials/basket_totals.html' with basket=frozen_basket %}
</div>
</div>

{% endblock order_contents %}

{% block place_order %}
<h3>{% trans "Please review the information above, then click 'Place Order'" %}</h3>
<form method="post" action="{% url paypal-place-order %}">
<form method="post" action="{% url paypal-place-order frozen_basket.id %}">
{% csrf_token %}
<input type="hidden" name="payer_id" value="{{ payer_id }}" />
<input type="hidden" name="token" value="{{ token }}" />
Expand Down