Skip to content
Open
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
56 changes: 54 additions & 2 deletions backend/bittan/bittan/tests/views/test_start_payment.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
from bittan.models.payment import PaymentStatus, PaymentMethod
from unittest.mock import patch
from bittan.models.question import QuestionType
from django.test import TestCase, Client


from datetime import datetime
from django.utils import timezone

from bittan.models import TicketType, ChapterEvent, Payment, Question
from bittan.models import Answer, AnswerSelectedOptions, TicketType, ChapterEvent, Payment, Question, QuestionOption
from bittan.models.question import QuestionType
from bittan.services.swish.swish import Swish

class StartPaymentTest(TestCase):
Expand Down Expand Up @@ -296,3 +296,55 @@ def test_already_paid_payment(self):

self.assertEqual(response.status_code, 403)

def test_form_payment(self):
q1 = Question.objects.create(
title = "Alkohol",
question_type = QuestionType.MULTIPLE_CHOICE,
chapter_event = self.test_event
)
q1_opt1 = QuestionOption.objects.create(
price = 100,
name = "Mycket",
has_text = False,
question=q1
)
q1_opt2 = QuestionOption.objects.create(
price = 0,
name = "Inget",
has_text = False,
question=q1
)
payment = Payment.objects.get(id=self.session_id)
ticket = payment.ticket_set.first()
answer = Answer.objects.create(
question = q1,
ticket=ticket
)
_ = AnswerSelectedOptions.objects.create(
question_option = q1_opt1,
answer = answer,
)
_ = AnswerSelectedOptions.objects.create(
question_option = q1_opt2,
answer = answer,
)
payment.status = PaymentStatus.FORM_SUBMITTED
payment.save()
response = self.client.post(
"/start_payment/",
{
"session_id": self.session_id
}
)

self.assertEqual(response.status_code, 200)

payment = Payment.objects.get(pk=self.session_id)
swish_payment_request = self.swish.get_payment_request(payment.swish_id)

self.assertEqual(payment.payment_started, True)
self.assertEqual(payment.status, PaymentStatus.FORM_SUBMITTED)
self.assertEqual(payment.payment_method, PaymentMethod.SWISH)
self.assertEqual(swish_payment_request.amount, 4*self.test_ticket.price + q1_opt1.price + q1_opt2.price)


146 changes: 78 additions & 68 deletions backend/bittan/bittan/views/start_payment.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from bittan.models.payment import PaymentMethod, PaymentStatus
from bittan.services.swish.swish_payment_request import SwishPaymentRequest

from bittan.models import Payment
from bittan.models import Payment, AnswerSelectedOptions, ChapterEvent

from bittan.services.swish.swish import Swish

Expand All @@ -13,6 +13,7 @@

from django.utils import timezone
from django.db.models import Sum
from django.db import transaction

import logging

Expand Down Expand Up @@ -41,75 +42,84 @@ def start_payment(request):
status=status.HTTP_404_NOT_FOUND
)

if payment.status == PaymentStatus.PAID:
return Response(
"AlreadyPaidPayment",
with transaction.atomic():
payment = Payment.objects.select_for_update().get(id=payment_id)
if payment.status == PaymentStatus.PAID:
return Response(
"AlreadyPaidPayment",
status=status.HTTP_403_FORBIDDEN
)

swish = Swish.get_instance() # Gets the swish instance that is global for the entire application.
if payment.payment_started:
return Response(swish.get_payment_request(payment.swish_id).token)

tickets = payment.ticket_set.select_for_update().all()

chapter_event = tickets.first().chapter_event
# Gets and locks the ChapterEvent. We seem to have get the chapter_event twice because of Django...
chapter_event = ChapterEvent.objects.select_for_update().get(pk=chapter_event.pk)

if payment.status == PaymentStatus.FAILED_EXPIRED_RESERVATION:
# TODO This comparison and update should also happen on the database
if chapter_event.fcfs and tickets.count() > chapter_event.total_seats - chapter_event.alive_ticket_count:
payment.status = PaymentStatus.FAILED_EXPIRED_RESERVATION
payment.save()
return Response(
"SessionExpired",
status=status.HTTP_408_REQUEST_TIMEOUT
)
payment.expires_at = timezone.now() + chapter_event.reservation_duration
payment.status = PaymentStatus.RESERVED
payment.save()


good_status = PaymentStatus.RESERVED
if not chapter_event.fcfs:
good_status = PaymentStatus.CONFIRMED
elif chapter_event.question_set.exists():
good_status = PaymentStatus.FORM_SUBMITTED

if payment.status != good_status:
return Response(
"PaymentNotPayable",
status=status.HTTP_403_FORBIDDEN
)

swish = Swish.get_instance() # Gets the swish intstance that is global for the entire application.
if payment.payment_started:
return Response(swish.get_payment_request(payment.swish_id).token)

tickets = payment.ticket_set.all()

chapter_event = tickets.first().chapter_event

if payment.status not in (PaymentStatus.RESERVED, PaymentStatus.FORM_SUBMITTED, PaymentStatus.CONFIRMED):
# TODO This comparison and update should also happen on the database
if chapter_event.fcfs and tickets.count() > chapter_event.total_seats - chapter_event.alive_ticket_count:
payment.status = PaymentStatus.FAILED_EXPIRED_RESERVATION
payment.save()
return Response(
"SessionExpired",
status=status.HTTP_408_REQUEST_TIMEOUT
)
payment.expires_at = timezone.now() + chapter_event.reservation_duration
payment.status = PaymentStatus.RESERVED
payment.save()

good_status = PaymentStatus.RESERVED
if not chapter_event.fcfs:
good_status = PaymentStatus.CONFIRMED
elif chapter_event.question_set.exists():
good_status = PaymentStatus.FORM_SUBMITTED

if payment.status != good_status:
return Response(
"PaymentNotPayable",
status=status.HTTP_403_FORBIDDEN
Payment.objects.filter(
pk = payment_id,
status = good_status, # These are just so that we are sure that the payment is in the required status. If it is not get will throw an error and that should be OK.
payment_started = False
).update(
payment_started = True
)

# "Atomically" update the payment status.
Payment.objects.filter(
pk = payment_id,
status = good_status, # These are just so that we are sure that the payment is in the required status. If it is not get will throw an error and that should be OK.
payment_started = False
).update(
payment_started = True
)

payment.refresh_from_db()

# payment.payment_started = True
logging.info(f"Started payment for payment with id {payment_id}")

total_price = tickets.aggregate(Sum("ticket_type__price"))["ticket_type__price__sum"]

swish = Swish.get_instance() # Gets the swish intstance that is global for the entire application.

payment_request: SwishPaymentRequest = swish.create_swish_payment(total_price, chapter_event.swish_message)

if payment_request.is_failed():
payment.PaymentStatus = PaymentStatus.FAILED_EXPIRED_RESERVATION
logging.warning(f"Payment with id {payment_id} did not get correctly initialised with Swish.")
return Response("PaymentStartFailed", status=status.HTTP_500_INTERNAL_SERVER_ERROR)


payment.swish_id = payment_request.id
payment.payment_method = PaymentMethod.SWISH
payment.save()
logging.info(f"Sucessfully initialised payment for payment with id {payment_id} with Swish.")

return Response(payment_request.token)

payment.refresh_from_db()

# payment.payment_started = True
logging.info(f"Started payment for payment with id {payment_id}")

ticket_price = tickets.aggregate(Sum("ticket_type__price"))["ticket_type__price__sum"]
form_price = AnswerSelectedOptions.objects.filter(
answer__ticket__in=tickets
).aggregate(
total = Sum("question_option__price")
)["total"] or 0

total_price = ticket_price + form_price
swish = Swish.get_instance() # Gets the swish intstance that is global for the entire application.
payment_request: SwishPaymentRequest = swish.create_swish_payment(total_price, chapter_event.swish_message)

if payment_request.is_failed():
transaction.rollback(True)
logging.warning(f"Payment with id {payment_id} did not get correctly initialised with Swish.")
return Response("PaymentStartFailed", status=status.HTTP_500_INTERNAL_SERVER_ERROR)


payment.swish_id = payment_request.id
payment.payment_method = PaymentMethod.SWISH
payment.save()
logging.info(f"Sucessfully initialised payment for payment with id {payment_id} with Swish.")

return Response(payment_request.token)

Loading