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)} )