vbv/server/vbv_lernwelt/shop/views.py

189 lines
6.0 KiB
Python

import structlog
from django.http import JsonResponse
from django.shortcuts import get_object_or_404
from rest_framework import status
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from vbv_lernwelt.shop.models import (
BillingAddress,
CheckoutInformation,
CheckoutState,
Product,
VV_PRODUCT_SKU,
)
from vbv_lernwelt.shop.serializers import BillingAddressSerializer
from vbv_lernwelt.shop.services import DataTransPaymentProvider
logger = structlog.get_logger(__name__)
URL_CHECKOUT_COMPLETE = "/onboarding/vv/checkout/complete"
URL_CHECKOUT_ADDRESS = "/onboarding/vv/checkout/address"
def checkout_error_url(base_url):
return f"{base_url}/onboarding/vv/checkout/address?error=true"
def checkout_cancel_url(base_url):
return f"{base_url}/"
def checkout_success_url(base_url):
return f"{base_url}/onboarding/vv/checkout/complete"
def webhook_url(base_url):
return f"{base_url}/api/shop/transaction/webhook"
@api_view(["GET"])
@permission_classes([IsAuthenticated])
def get_billing_address(request):
billing_address = get_object_or_404(BillingAddress, user=request.user)
serializer = BillingAddressSerializer(billing_address)
return Response(serializer.data)
@api_view(["PUT"])
@permission_classes([IsAuthenticated])
def update_billing_address(request):
try:
billing_address = BillingAddress.objects.get(user=request.user)
except BillingAddress.DoesNotExist:
billing_address = None
serializer = BillingAddressSerializer(
billing_address, data=request.data, partial=True
)
if serializer.is_valid():
serializer.save(user=request.user)
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
@api_view(["POST"])
def transaction_webhook(request):
"""Webhook endpoint for Datatrans to notify about transaction state changes."""
logger.info("Transaction Webhook Received", body=request.body)
datatrans = DataTransPaymentProvider()
# FIXME verify signature, not working yet
# if not datatrans.is_webhook_request_signature_valid(
# request=request,
# ):
# logger.error("Invalid webhook signature")
# return JsonResponse({"status": "error"}, status=status.HTTP_400_BAD_REQUEST)
transaction = request.data
transaction_id = transaction["transactionId"]
transaction_status = transaction["status"]
# FIXME just pass the checkout_id as sequence number?
checkout_info = CheckoutInformation.objects.get(transaction_id=transaction_id)
checkout_info.state = transaction_status
checkout_info.webhook_history.append(transaction)
checkout_info.save(update_fields=["state", "webhook_history"])
if transaction_status == ["settled", "transmitted"]:
# FIXME create CSU
...
return JsonResponse({"status": "ok"})
@api_view(["POST"])
@permission_classes([IsAuthenticated])
def checkout_vv(request):
"""
Checkout for the Versicherungsvermittler product (VV).
"""
# The one and only product, thus hardcoded
# Expected to be created in the admin interface first.
sku = VV_PRODUCT_SKU
logger.info("Checkout requested", sku=sku, user_id=request.user.id)
try:
product = Product.objects.get(sku=sku)
except Product.DoesNotExist:
raise Exception(
f"Required Product not found: {sku} must be created in the admin interface first.",
)
checkouts = CheckoutInformation.objects.filter(
user=request.user,
product_sku=sku,
)
datatrans = DataTransPaymentProvider()
# already purchased (settled or transmitted)
if checkouts.filter(
state__in=[CheckoutState.SETTLED, CheckoutState.TRANSMITTED]
).exists():
return JsonResponse({"next_step_url": URL_CHECKOUT_COMPLETE})
# FIXME: Re-using seems not to work -> just create a new one for now
# Also check if failed transactions get notified via webhook
# already initialized -> redirect to payment page again
# if checkout := checkouts.filter(state=CheckoutState.INITIALIZED).first():
# return JsonResponse(
# {
# "next_step_url": datatrans.get_payment_url(
# transaction_id=checkout.transaction_id
# )
# }
# )
address = request.data["address"]
organization = request.data["organisation_address"]
base_redirect_url = request.data["redirect_url"]
# not yet initialized at all or canceled/failed
transaction_id = datatrans.init_transaction(
user=request.user,
amount_chf_centimes=product.price,
redirect_url_success=checkout_success_url(base_redirect_url),
redirect_url_error=checkout_error_url(base_redirect_url),
redirect_url_cancel=checkout_cancel_url(base_redirect_url),
webhook_url=webhook_url(base_redirect_url),
)
# immutable snapshot of data at time of purchase
CheckoutInformation.objects.create(
user=request.user,
state="initialized",
transaction_id=transaction_id,
# product
product_sku=sku,
product_price=product.price,
product_name=product.name,
product_description=product.description,
# address
first_name=address["firstName"],
last_name=address["lastName"],
street=address["street"],
street_number=address["streetNumber"],
postal_code=address["postalCode"],
city=address["city"],
country=address["country"],
# organization
company_name=organization.get("name"),
company_street=organization.get("street"),
company_street_number=organization.get("streetNumber"),
company_postal_code=organization.get("postalCode"),
company_city=organization.get("city"),
company_country=organization.get("country"),
)
return JsonResponse(
{"next_step_url": datatrans.get_payment_url(transaction_id=transaction_id)}
)