Skip to content

Commit

Permalink
ledger: add protection for checkout dying at last hop
Browse files Browse the repository at this point in the history
  • Loading branch information
Scott Percival committed Mar 8, 2018
1 parent d2dd273 commit bc4a488
Show file tree
Hide file tree
Showing 7 changed files with 80 additions and 40 deletions.
12 changes: 11 additions & 1 deletion ledger/checkout/mixins.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import requests

from oscar.core.loading import get_class
from ledger.payments.models import Invoice
from django.http import HttpResponseRedirect
Expand Down Expand Up @@ -47,6 +49,7 @@ def handle_successful_order(self, order):
from ledger.payments.utils import update_payments
# Get the return url
return_url = self.checkout_session.return_url()
return_preload_url = self.checkout_session.return_preload_url()
force_redirect = self.checkout_session.force_redirect()

# Update the payments in the order lines
Expand All @@ -60,14 +63,21 @@ def handle_successful_order(self, order):
# Flush all session data
self.checkout_session.flush()

# If preload is enabled, fire off an unmonitored request server-side
# FIXME: replace with basket one-time secret
if return_preload_url:
requests.get('{}?invoice={}'.format(return_preload_url, invoice.reference),
cookies=self.request.COOKIES)

# Save order and invoice id in session so thank-you page can load it
self.request.session['checkout_order_id'] = order.id
self.request.session['checkout_return_url'] = return_url

if not force_redirect:
response = HttpResponseRedirect(self.get_success_url())
else:
response = HttpResponseRedirect('{}?invoice={}'.format(return_url, Invoice.objects.get(order_number=order.number).reference))
response = HttpResponseRedirect('{}?invoice={}'.format(return_url, invoice.reference))

self.send_signal(self.request, response, order)

return response
6 changes: 6 additions & 0 deletions ledger/checkout/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ def return_to(self, url):
def return_url(self):
return self._get('ledger','return_url')

def return_preload_to(self, url):
self._set('ledger', 'return_preload_url', url)

def return_preload_url(self):
return self._get('ledger', 'return_preload_url')

# Template Methods
# ===========================
def use_template(self, url):
Expand Down
11 changes: 9 additions & 2 deletions ledger/checkout/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ def validate_ledger(self,details):
self.__validate_system(self.request.basket.system)
# validate return url
self.__validate_url(details.get('return_url'),'return')
self.__validate_url(details.get('return_preload_url'),'return_preload')
# validate bpay if present
self.__validate_bpay(details.get('bpay_details'))
# validate basket owner if present
Expand Down Expand Up @@ -151,8 +152,13 @@ def __validate_url(self, url, _type):
messages.error(self.request,msg)
raise self.FallbackMissing()
# Check if the url works
checkURL(url)
self.checkout_session.return_to(url)
if _type == 'return':
checkURL(url)
self.checkout_session.return_to(url)
elif _type == 'fallback':
checkURL(url)
elif _type == 'return_preload':
self.checkout_session.return_preload_to(url)

def __validate_card_method(self, method):
''' Validate if the card method is payment or preauth
Expand Down Expand Up @@ -212,6 +218,7 @@ def get(self, request, *args, **kwargs):
'template': request.GET.get('template',None),
'fallback_url': request.GET.get('fallback_url',None),
'return_url': request.GET.get('return_url',None),
'return_preload_url': request.GET.get('return_preload_url', None),
'associateInvoiceWithToken': request.GET.get('associateInvoiceWithToken',False),
'forceRedirect': request.GET.get('forceRedirect',False),
'sendEmail': request.GET.get('sendEmail',False),
Expand Down
4 changes: 2 additions & 2 deletions ledger/payments/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -746,6 +746,7 @@ class CheckoutSerializer(serializers.Serializer):
basket_owner = serializers.IntegerField(required=False)
template = serializers.CharField(required=False)
fallback_url = serializers.URLField()
return_preload_url = serializers.URLField(required=False)
return_url = serializers.URLField()
associateInvoiceWithToken = serializers.BooleanField(default=False)
forceRedirect = serializers.BooleanField(default=False)
Expand Down Expand Up @@ -864,11 +865,10 @@ def create(self, request):
basket = createBasket(serializer.validated_data['products'],request.user,serializer.validated_data['system'])

fields = [
'card_method', 'basket_owner', 'template', 'fallback_url', 'return_url', 'associateInvoiceWithToken', 'forceRedirect', 'sendEmail', 'proxy',
'card_method', 'basket_owner', 'template', 'fallback_url', 'return_url', 'return_preload_url', 'associateInvoiceWithToken', 'forceRedirect', 'sendEmail', 'proxy',
'checkoutWithToken', 'bpay_format', 'icrn_format', 'invoice_text', 'check_url'
]
url_args = {f: six.text_type(serializer.validated_data[f]).encode('utf8') for f in fields if f in serializer.validated_data and serializer.validated_data[f] is not None}

redirect = HttpResponseRedirect(reverse('checkout:index')+'?'+six.moves.urllib.parse.urlencode(url_args))
# inject the current basket into the redirect response cookies
# or else, anonymous users will be directionless
Expand Down
4 changes: 4 additions & 0 deletions parkstay/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@

class BookingRangeWithinException(Exception):
pass


class BindBookingException(Exception):
pass
42 changes: 42 additions & 0 deletions parkstay/utils.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from datetime import datetime, timedelta, date
import logging
import traceback
from decimal import *
import json
Expand All @@ -15,6 +16,9 @@
from parkstay.models import (Campground, Campsite, CampsiteRate, CampsiteBooking, Booking, BookingInvoice, CampsiteBookingRange, Rate, CampgroundBookingRange,CampgroundStayHistory, CampsiteRate, ParkEntryRate, BookingVehicleRego)
from parkstay.serialisers import BookingRegoSerializer, CampsiteRateSerializer, ParkEntryRateSerializer,RateSerializer,CampsiteRateReadonlySerializer
from parkstay.emails import send_booking_invoice,send_booking_confirmation
from parkstay.exceptions import BindBookingException

logger = logging.getLogger('booking_checkout')


def create_booking_by_class(campground_id, campsite_class_id, start_date, end_date, num_adult=0, num_concession=0, num_child=0, num_infant=0):
Expand Down Expand Up @@ -779,6 +783,7 @@ def checkout(request, booking, lines, invoice_text=None, vouchers=[], internal=F
'system': settings.PS_PAYMENT_SYSTEM_ID,
'fallback_url': request.build_absolute_uri('/'),
'return_url': request.build_absolute_uri(reverse('public_booking_success')),
'return_preload_url': request.build_absolute_uri(reverse('public_booking_success')),
'forceRedirect': True,
'proxy': True if internal else False,
"products": lines,
Expand Down Expand Up @@ -878,6 +883,43 @@ def delete_session_booking(session):
del session['ps_booking']
session.modified = True


def bind_booking(request, booking, invoice_ref):
if booking.booking_type == 3:
try:
inv = Invoice.objects.get(reference=invoice_ref)
except Invoice.DoesNotExist:
logger.error(u'{} tried making a booking with an incorrect invoice'.format(u'User {} with id {}'.format(booking.customer.get_full_name(),booking.customer.id) if booking.customer else u'An anonymous user'))
raise BindBookingException

if inv.system not in ['0019']:
logger.error(u'{} tried making a booking with an invoice from another system with reference number {}'.format(u'User {} with id {}'.format(booking.customer.get_full_name(),booking.customer.id) if booking.customer else u'An anonymous user',inv.reference))
raise BindBookingException

try:
b = BookingInvoice.objects.get(invoice_reference=invoice_ref)
logger.error(u'{} tried making a booking with an already used invoice with reference number {}'.format(u'User {} with id {}'.format(booking.customer.get_full_name(),booking.customer.id) if booking.customer else u'An anonymous user',inv.reference))
raise BindBookingException
except BookingInvoice.DoesNotExist:
logger.info(u'{} finished temporary booking {}, creating new BookingInvoice with reference {}'.format(u'User {} with id {}'.format(booking.customer.get_full_name(),booking.customer.id) if booking.customer else u'An anonymous user',booking.id, invoice_ref))
# FIXME: replace with server side notify_url callback
book_inv, created = BookingInvoice.objects.get_or_create(booking=booking, invoice_reference=invoice_ref)

# set booking to be permanent fixture
booking.booking_type = 1 # internet booking
booking.expiry_time = None
booking.save()

delete_session_booking(request.session)
request.session['ps_last_booking'] = booking.id

# send out the invoice before the confirmation is sent
send_booking_invoice(booking)
# for fully paid bookings, fire off confirmation email
if booking.paid:
emails.send_booking_confirmation(booking,request)


def daterange(start_date, end_date):
for n in range(int ((end_date - start_date).days)):
yield start_date + timedelta(n)
Expand Down
41 changes: 6 additions & 35 deletions parkstay/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from django.views.decorators.csrf import ensure_csrf_cookie
from django import forms
from parkstay.forms import LoginForm, MakeBookingsForm, AnonymousMakeBookingsForm, VehicleInfoFormset
from parkstay.exceptions import BindBookingException
from parkstay.models import (Campground,
CampsiteBooking,
Campsite,
Expand All @@ -27,7 +28,6 @@
CampsiteRate,
ParkEntryRate
)
from parkstay import emails
from ledger.accounts.models import EmailUser, Address
from ledger.payments.models import Invoice
from django_ical.views import ICalFeed
Expand Down Expand Up @@ -317,40 +317,11 @@ def get(self, request, *args, **kwargs):
booking = utils.get_session_booking(request.session)
invoice_ref = request.GET.get('invoice')

if booking.booking_type == 3:
try:
inv = Invoice.objects.get(reference=invoice_ref)
except Invoice.DoesNotExist:
logger.error(u'{} tried making a booking with an incorrect invoice'.format(u'User {} with id {}'.format(booking.customer.get_full_name(),booking.customer.id) if booking.customer else u'An anonymous user'))
return redirect('public_make_booking')

if inv.system not in ['0019']:
logger.error(u'{} tried making a booking with an invoice from another system with reference number {}'.format(u'User {} with id {}'.format(booking.customer.get_full_name(),booking.customer.id) if booking.customer else u'An anonymous user',inv.reference))
return redirect('public_make_booking')

try:
b = BookingInvoice.objects.get(invoice_reference=invoice_ref)
logger.error(u'{} tried making a booking with an already used invoice with reference number {}'.format(u'User {} with id {}'.format(booking.customer.get_full_name(),booking.customer.id) if booking.customer else u'An anonymous user',inv.reference))
return redirect('public_make_booking')
except BookingInvoice.DoesNotExist:
logger.info(u'{} finished temporary booking {}, creating new BookingInvoice with reference {}'.format(u'User {} with id {}'.format(booking.customer.get_full_name(),booking.customer.id) if booking.customer else u'An anonymous user',booking.id, invoice_ref))
# FIXME: replace with server side notify_url callback
book_inv, created = BookingInvoice.objects.get_or_create(booking=booking, invoice_reference=invoice_ref)

# set booking to be permanent fixture
booking.booking_type = 1 # internet booking
booking.expiry_time = None
booking.save()

utils.delete_session_booking(request.session)
request.session['ps_last_booking'] = booking.id

# send out the invoice before the confirmation is sent
emails.send_booking_invoice(booking)
# for fully paid bookings, fire off confirmation email
if booking.paid:
emails.send_booking_confirmation(booking,request)

try:
utils.bind_booking(request, booking, invoice_ref)
except BindBookingException:
return redirect('public_make_booking')

except Exception as e:
if 'ps_booking_internal' in request.COOKIES:
return redirect('home')
Expand Down

0 comments on commit bc4a488

Please sign in to comment.