From d40557bbbac070235919ee8b90e3e6f185db0520 Mon Sep 17 00:00:00 2001 From: Mark Veidemanis Date: Thu, 21 Jul 2022 13:50:45 +0100 Subject: [PATCH] Implement cancelling subscriptions --- app/local_settings.example.py | 1 + app/urls.py | 7 ++- core/templates/index.html | 55 +++++++++++------------ core/templates/partials/product-list.html | 2 +- core/templates/subscriptioncancel.html | 9 ++++ core/views/__init__.py | 21 +++++++++ core/views/callbacks.py | 23 +++++----- 7 files changed, 77 insertions(+), 41 deletions(-) create mode 100644 core/templates/subscriptioncancel.html diff --git a/app/local_settings.example.py b/app/local_settings.example.py index fa54526..42ba9fa 100644 --- a/app/local_settings.example.py +++ b/app/local_settings.example.py @@ -43,3 +43,4 @@ STRIPE_PUBLIC_API_KEY_PROD = "pk_prod_xxx" STRIPE_ENDPOINT_SECRET = "" STATIC_ROOT = "" +SECRET_KEY = "a" diff --git a/app/urls.py b/app/urls.py index 800d2c2..0a88d94 100644 --- a/app/urls.py +++ b/app/urls.py @@ -20,7 +20,7 @@ from django.urls import include, path from django.views.generic import TemplateView from core.ui.views.drilldown import Drilldown -from core.views import Billing, Home, Order, Portal, Signup +from core.views import Billing, Cancel, Home, Order, Portal, Signup from core.views.callbacks import Callback from core.views.charts import SentimentChartJSONView, VolumeChartJSONView @@ -29,6 +29,11 @@ urlpatterns = [ path("callback", Callback.as_view(), name="callback"), path("billing/", Billing.as_view(), name="billing"), path("order//", Order.as_view(), name="order"), + path( + "cancel_subscription//", + Cancel.as_view(), + name="cancel_subscription", + ), path( "success/", TemplateView.as_view(template_name="success.html"), name="success" ), diff --git a/core/templates/index.html b/core/templates/index.html index edb4add..d8b4138 100644 --- a/core/templates/index.html +++ b/core/templates/index.html @@ -8,41 +8,40 @@

{{ block.title }}

{% endif %}
-
- {% if block.column1 is not None %} -
- +
+ {% if block.column1 is not None %} +
{{ block.column1 }}
- {% endif %} - {% if block.column2 is not None %} -
+ {% endif %} + {% if block.column2 is not None %} +
{{ block.column2 }} -
- {% endif %} - {% if block.column3 is not None %} -
+
+ {% endif %} + {% if block.column3 is not None %} +
{{ block.column3 }} -
- {% endif %} -
-
- {% if block.image1 is not None %} -
+
+ {% endif %} +
+
+ {% if block.image1 is not None %} +
-
- {% endif %} - {% if block.image2 is not None %} -
+
+ {% endif %} + {% if block.image2 is not None %} +
-
- {% endif %} - {% if block.image3 is not None %} -
+
+ {% endif %} + {% if block.image3 is not None %} +
-
- {% endif %} -
+
+ {% endif %} +
{% endfor %}
diff --git a/core/templates/partials/product-list.html b/core/templates/partials/product-list.html index b9b31a2..9d845b3 100644 --- a/core/templates/partials/product-list.html +++ b/core/templates/partials/product-list.html @@ -32,7 +32,7 @@ {% endif %} {% if plan in user_plans %} - + diff --git a/core/templates/subscriptioncancel.html b/core/templates/subscriptioncancel.html new file mode 100644 index 0000000..fe63b0e --- /dev/null +++ b/core/templates/subscriptioncancel.html @@ -0,0 +1,9 @@ +{% extends "base.html" %} + +{% block content %} + +
+

Subscription {{ plan }} cancelled!

+
+ +{% endblock %} diff --git a/core/views/__init__.py b/core/views/__init__.py index 3b53290..f4f909c 100644 --- a/core/views/__init__.py +++ b/core/views/__init__.py @@ -1,3 +1,4 @@ +import logging import pprint import stripe @@ -13,6 +14,7 @@ from core.forms import NewUserForm from core.lib.products import assemble_plan_map from core.models import ContentBlock, Plan, Session +logger = logging.getLogger(__name__) pp = pprint.PrettyPrinter(indent=4) # Create your views here @@ -55,6 +57,25 @@ class Order(LoginRequiredMixin, View): return JsonResponse({"error": str(e)}, status=500) +class Cancel(LoginRequiredMixin, View): + def get(self, request, plan_name): + plan = Plan.objects.get(name=plan_name) + try: + subscriptions = stripe.Subscription.list( + customer=request.user.stripe_id, price=plan.product_id + ) + for subscription in subscriptions["data"]: + items = subscription["items"]["data"] + for item in items: + stripe.Subscription.delete(item["subscription"]) + return render(request, "subscriptioncancel.html", {"plan": plan}) + # return JsonResponse({'id': session.id}) + except Exception as e: + # Raise a server error + logging.error(f"Error cancelling subscription for user: {e}") + return JsonResponse({"error": "True"}, status=500) + + class Signup(CreateView): form_class = NewUserForm success_url = reverse_lazy("login") diff --git a/core/views/callbacks.py b/core/views/callbacks.py index a09be9c..450561a 100644 --- a/core/views/callbacks.py +++ b/core/views/callbacks.py @@ -1,3 +1,4 @@ +import logging from datetime import datetime import stripe @@ -9,6 +10,8 @@ from rest_framework.views import APIView from core.models import Plan, Session, User +logger = logging.getLogger(__name__) + class Callback(APIView): parser_classes = [JSONParser] @@ -17,16 +20,17 @@ class Callback(APIView): def post(self, request): payload = request.body sig_header = request.META["HTTP_STRIPE_SIGNATURE"] - try: stripe.Webhook.construct_event( payload, sig_header, settings.STRIPE_ENDPOINT_SECRET ) except ValueError: # Invalid payload + logger.error("Invalid payload") return HttpResponse(status=400) except stripe.error.SignatureVerificationError: # Invalid signature + logger.error("Invalid signature") return HttpResponse(status=400) if request.data is None: @@ -37,7 +41,6 @@ class Callback(APIView): session = request.data["data"]["object"]["id"] subscription_id = request.data["data"]["object"]["subscription"] session_map = Session.objects.get(session=session) - print("querying session", session) if not session_map: return JsonResponse({"success": False}, status=500) user = session_map.user @@ -47,7 +50,7 @@ class Callback(APIView): if rtype == "customer.subscription.updated": stripe_id = request.data["data"]["object"]["customer"] if not stripe_id: - print("No user found for customer:", stripe_id) + logging.error("No stripe id") return JsonResponse({"success": False}, status=500) user = User.objects.get(stripe_id=stripe_id) # ssubscription_active @@ -58,7 +61,9 @@ class Callback(APIView): if session_iter.subscription_id == subscription_id: session = session_iter if not session: - print(f"No session found for subscription id {subscription_id}") + logging.error( + f"No session found for subscription id {subscription_id}" + ) return JsonResponse({"success": False}, status=500) # query Session objects # iterate and check against product_id @@ -66,24 +71,20 @@ class Callback(APIView): product_id = request.data["data"]["object"]["plan"]["id"] plan = Plan.objects.get(product_id=product_id) if not plan: - print(f"Plan not found: {product_id}") + logging.error(f"Plan not found: {product_id}") return JsonResponse({"success": False}, status=500) session.plan = plan session.save() elif rtype == "payment_intent.succeeded": customer = request.data["data"]["object"]["customer"] - print("customer", customer) user = User.objects.get(stripe_id=customer) if not user: - print("No user found for customer:", customer) + logging.error(f"No user found for customer: {customer}") return JsonResponse({"success": False}, status=500) - print("got", user.email) session = Session.objects.get(request=request.data["request"]["id"]) - print("Got session", session) user.plans.add(session.plan) - print("ADDING PLAN TO USER PLANS") user.last_payment = datetime.utcnow() user.save() @@ -91,7 +92,7 @@ class Callback(APIView): customer = request.data["data"]["object"]["customer"] user = User.objects.get(stripe_id=customer) if not user: - print("No user found for customer:", customer) + logging.error(f"No user found for customer {customer}") return JsonResponse({"success": False}, status=500) product_id = request.data["data"]["object"]["plan"]["id"] plan = Plan.objects.get(product_id=product_id)