import structlog from django.http import JsonResponse 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 ( get_payment_url, init_transaction, is_signature_valid, ) logger = structlog.get_logger(__name__) @api_view(["GET"]) @permission_classes([IsAuthenticated]) def get_billing_address(request): try: billing_address = BillingAddress.objects.get(user=request.user) data = BillingAddressSerializer(billing_address).data except BillingAddress.DoesNotExist: data = BillingAddressSerializer().data data["first_name"] = request.user.first_name data["last_name"] = request.user.last_name return Response(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): logger.info("Webhook: Datatrans called transaction webhook", body=request.body) if not is_signature_valid( signature=request.headers.get("Datatrans-Signature", ""), payload=request.body, ): logger.warning("Invalid signature") return JsonResponse({"status": "invalid signature"}, status=400) transaction = request.data transaction_id = transaction["transactionId"] transaction_status = transaction["status"] 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). VV_PRODUCT_SKU: The one and only product, thus hardcoded Expected to be created in the admin interface first. """ sku = VV_PRODUCT_SKU logger.info(f"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, ) # already purchased (settled or transmitted) if checkouts.filter( state__in=[CheckoutState.SETTLED, CheckoutState.TRANSMITTED] ).exists(): return JsonResponse({"next_step_url": checkout_success_url()}) # already initialized -> redirect to payment page again if checkout := checkouts.filter(state=CheckoutState.INITIALIZED).first(): # FIXME: Ask datatrans how they advice to handle this case: # - 1. Why is the payment url not reusable? Calling the payment url again # with the same transaction id results in a blank page. # - 2. After 30 minutes, the transaction is fails but our webhook is not called? return JsonResponse({"next_step_url": get_payment_url(checkout.transaction_id)}) # not yet initialized at all, or canceled/failed base_redirect_url = request.data["redirect_url"] transaction_id = 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 **request.data["address"], ) return JsonResponse({"next_step_url": get_payment_url(transaction_id)}) def webhook_url(base_url: str) -> str: return f"{base_url}/api/shop/transaction/webhook" def checkout_error_url(base_url: str) -> str: return f"{base_url}/onboarding/vv/checkout/address?error=true" def checkout_cancel_url(base_url: str) -> str: return f"{base_url}/" def checkout_success_url(base_url: str = "") -> str: return f"{base_url}/onboarding/vv/checkout/complete"