Implement cancelling subscriptions

This commit is contained in:
Mark Veidemanis 2022-07-21 13:50:45 +01:00
parent db87a138f2
commit d40557bbba
Signed by: m
GPG Key ID: 5ACFCEED46C0904F
7 changed files with 77 additions and 41 deletions

View File

@ -43,3 +43,4 @@ STRIPE_PUBLIC_API_KEY_PROD = "pk_prod_xxx"
STRIPE_ENDPOINT_SECRET = "" STRIPE_ENDPOINT_SECRET = ""
STATIC_ROOT = "" STATIC_ROOT = ""
SECRET_KEY = "a"

View File

@ -20,7 +20,7 @@ from django.urls import include, path
from django.views.generic import TemplateView from django.views.generic import TemplateView
from core.ui.views.drilldown import Drilldown 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.callbacks import Callback
from core.views.charts import SentimentChartJSONView, VolumeChartJSONView from core.views.charts import SentimentChartJSONView, VolumeChartJSONView
@ -29,6 +29,11 @@ urlpatterns = [
path("callback", Callback.as_view(), name="callback"), path("callback", Callback.as_view(), name="callback"),
path("billing/", Billing.as_view(), name="billing"), path("billing/", Billing.as_view(), name="billing"),
path("order/<str:plan_name>/", Order.as_view(), name="order"), path("order/<str:plan_name>/", Order.as_view(), name="order"),
path(
"cancel_subscription/<str:plan_name>/",
Cancel.as_view(),
name="cancel_subscription",
),
path( path(
"success/", TemplateView.as_view(template_name="success.html"), name="success" "success/", TemplateView.as_view(template_name="success.html"), name="success"
), ),

View File

@ -8,41 +8,40 @@
<h1 class="title">{{ block.title }}</h1> <h1 class="title">{{ block.title }}</h1>
{% endif %} {% endif %}
<div class="box"> <div class="box">
<div class="columns"> <div class="columns">
{% if block.column1 is not None %} {% if block.column1 is not None %}
<div class="column"> <div class="column">
{{ block.column1 }} {{ block.column1 }}
</div> </div>
{% endif %} {% endif %}
{% if block.column2 is not None %} {% if block.column2 is not None %}
<div class="column"> <div class="column">
{{ block.column2 }} {{ block.column2 }}
</div> </div>
{% endif %} {% endif %}
{% if block.column3 is not None %} {% if block.column3 is not None %}
<div class="column"> <div class="column">
{{ block.column3 }} {{ block.column3 }}
</div> </div>
{% endif %} {% endif %}
</div> </div>
<div class="columns"> <div class="columns">
{% if block.image1 is not None %} {% if block.image1 is not None %}
<div class="column"> <div class="column">
<img src="{% static block.image1 %}"> <img src="{% static block.image1 %}">
</div> </div>
{% endif %} {% endif %}
{% if block.image2 is not None %} {% if block.image2 is not None %}
<div class="column"> <div class="column">
<img src="{% static block.image2 %}"> <img src="{% static block.image2 %}">
</div> </div>
{% endif %} {% endif %}
{% if block.image3 is not None %} {% if block.image3 is not None %}
<div class="column"> <div class="column">
<img src="{% static block.image3 %}"> <img src="{% static block.image3 %}">
</div> </div>
{% endif %} {% endif %}
</div> </div>
</div> </div>
{% endfor %} {% endfor %}
</div> </div>

View File

@ -32,7 +32,7 @@
{% endif %} {% endif %}
{% if plan in user_plans %} {% if plan in user_plans %}
<a class="level-item" href="/cancel/{{ plan.name }}"> <a class="level-item" href="/cancel_subscription/{{ plan.name }}">
<span class="icon is-small has-text-info"> <span class="icon is-small has-text-info">
<i class="fas fa-cancel" aria-hidden="true"></i> <i class="fas fa-cancel" aria-hidden="true"></i>
</span> </span>

View File

@ -0,0 +1,9 @@
{% extends "base.html" %}
{% block content %}
<section>
<p>Subscription {{ plan }} cancelled!</p>
</section>
{% endblock %}

View File

@ -1,3 +1,4 @@
import logging
import pprint import pprint
import stripe import stripe
@ -13,6 +14,7 @@ from core.forms import NewUserForm
from core.lib.products import assemble_plan_map from core.lib.products import assemble_plan_map
from core.models import ContentBlock, Plan, Session from core.models import ContentBlock, Plan, Session
logger = logging.getLogger(__name__)
pp = pprint.PrettyPrinter(indent=4) pp = pprint.PrettyPrinter(indent=4)
# Create your views here # Create your views here
@ -55,6 +57,25 @@ class Order(LoginRequiredMixin, View):
return JsonResponse({"error": str(e)}, status=500) 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): class Signup(CreateView):
form_class = NewUserForm form_class = NewUserForm
success_url = reverse_lazy("login") success_url = reverse_lazy("login")

View File

@ -1,3 +1,4 @@
import logging
from datetime import datetime from datetime import datetime
import stripe import stripe
@ -9,6 +10,8 @@ from rest_framework.views import APIView
from core.models import Plan, Session, User from core.models import Plan, Session, User
logger = logging.getLogger(__name__)
class Callback(APIView): class Callback(APIView):
parser_classes = [JSONParser] parser_classes = [JSONParser]
@ -17,16 +20,17 @@ class Callback(APIView):
def post(self, request): def post(self, request):
payload = request.body payload = request.body
sig_header = request.META["HTTP_STRIPE_SIGNATURE"] sig_header = request.META["HTTP_STRIPE_SIGNATURE"]
try: try:
stripe.Webhook.construct_event( stripe.Webhook.construct_event(
payload, sig_header, settings.STRIPE_ENDPOINT_SECRET payload, sig_header, settings.STRIPE_ENDPOINT_SECRET
) )
except ValueError: except ValueError:
# Invalid payload # Invalid payload
logger.error("Invalid payload")
return HttpResponse(status=400) return HttpResponse(status=400)
except stripe.error.SignatureVerificationError: except stripe.error.SignatureVerificationError:
# Invalid signature # Invalid signature
logger.error("Invalid signature")
return HttpResponse(status=400) return HttpResponse(status=400)
if request.data is None: if request.data is None:
@ -37,7 +41,6 @@ class Callback(APIView):
session = request.data["data"]["object"]["id"] session = request.data["data"]["object"]["id"]
subscription_id = request.data["data"]["object"]["subscription"] subscription_id = request.data["data"]["object"]["subscription"]
session_map = Session.objects.get(session=session) session_map = Session.objects.get(session=session)
print("querying session", session)
if not session_map: if not session_map:
return JsonResponse({"success": False}, status=500) return JsonResponse({"success": False}, status=500)
user = session_map.user user = session_map.user
@ -47,7 +50,7 @@ class Callback(APIView):
if rtype == "customer.subscription.updated": if rtype == "customer.subscription.updated":
stripe_id = request.data["data"]["object"]["customer"] stripe_id = request.data["data"]["object"]["customer"]
if not stripe_id: if not stripe_id:
print("No user found for customer:", stripe_id) logging.error("No stripe id")
return JsonResponse({"success": False}, status=500) return JsonResponse({"success": False}, status=500)
user = User.objects.get(stripe_id=stripe_id) user = User.objects.get(stripe_id=stripe_id)
# ssubscription_active # ssubscription_active
@ -58,7 +61,9 @@ class Callback(APIView):
if session_iter.subscription_id == subscription_id: if session_iter.subscription_id == subscription_id:
session = session_iter session = session_iter
if not session: 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) return JsonResponse({"success": False}, status=500)
# query Session objects # query Session objects
# iterate and check against product_id # iterate and check against product_id
@ -66,24 +71,20 @@ class Callback(APIView):
product_id = request.data["data"]["object"]["plan"]["id"] product_id = request.data["data"]["object"]["plan"]["id"]
plan = Plan.objects.get(product_id=product_id) plan = Plan.objects.get(product_id=product_id)
if not plan: if not plan:
print(f"Plan not found: {product_id}") logging.error(f"Plan not found: {product_id}")
return JsonResponse({"success": False}, status=500) return JsonResponse({"success": False}, status=500)
session.plan = plan session.plan = plan
session.save() session.save()
elif rtype == "payment_intent.succeeded": elif rtype == "payment_intent.succeeded":
customer = request.data["data"]["object"]["customer"] customer = request.data["data"]["object"]["customer"]
print("customer", customer)
user = User.objects.get(stripe_id=customer) user = User.objects.get(stripe_id=customer)
if not user: 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) return JsonResponse({"success": False}, status=500)
print("got", user.email)
session = Session.objects.get(request=request.data["request"]["id"]) session = Session.objects.get(request=request.data["request"]["id"])
print("Got session", session)
user.plans.add(session.plan) user.plans.add(session.plan)
print("ADDING PLAN TO USER PLANS")
user.last_payment = datetime.utcnow() user.last_payment = datetime.utcnow()
user.save() user.save()
@ -91,7 +92,7 @@ class Callback(APIView):
customer = request.data["data"]["object"]["customer"] customer = request.data["data"]["object"]["customer"]
user = User.objects.get(stripe_id=customer) user = User.objects.get(stripe_id=customer)
if not user: 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) return JsonResponse({"success": False}, status=500)
product_id = request.data["data"]["object"]["plan"]["id"] product_id = request.data["data"]["object"]["plan"]["id"]
plan = Plan.objects.get(product_id=product_id) plan = Plan.objects.get(product_id=product_id)