diff --git a/subscribie/blueprints/admin/__init__.py b/subscribie/blueprints/admin/__init__.py
index e01dac376..174fff42e 100644
--- a/subscribie/blueprints/admin/__init__.py
+++ b/subscribie/blueprints/admin/__init__.py
@@ -81,6 +81,7 @@
get_number_of_signups,
get_number_of_one_off_purchases,
get_number_of_transactions_with_donations,
+ get_number_of_recent_subscription_cancellations,
)
import stripe
@@ -487,6 +488,9 @@ def dashboard():
num_signups = get_number_of_signups()
num_one_off_purchases = get_number_of_one_off_purchases()
num_donations = get_number_of_transactions_with_donations()
+ num_recent_subscription_cancellations = (
+ get_number_of_recent_subscription_cancellations()
+ )
shop_default_country_code = get_shop_default_country_code()
saas_url = current_app.config.get("SAAS_URL")
@@ -500,6 +504,7 @@ def dashboard():
num_signups=num_signups,
num_donations=num_donations,
num_one_off_purchases=num_one_off_purchases,
+ num_recent_subscription_cancellations=num_recent_subscription_cancellations,
shop_default_country_code=shop_default_country_code,
saas_url=saas_url,
)
@@ -1292,6 +1297,53 @@ def subscribers():
)
+@admin.route("/recent-subscription-cancellations")
+@login_required
+def show_recent_subscription_cancellations():
+ """Get the last 30 days subscription cancellations (if any)
+ Note: Stripe api only guarentees the last 30 days of events.
+ At time of writing this method performs no caching of events,
+ see StripeInvoice for possible improvements
+ """
+ stripe.api_key = get_stripe_secret_key()
+ connect_account = get_stripe_connect_account()
+
+ subscription_cancellations = stripe.Event.list(
+ stripe_account=connect_account.id,
+ limit=100,
+ types=["customer.subscription.deleted"],
+ )
+ cancellations = []
+ # subscription id
+ for index, value in enumerate(subscription_cancellations):
+ # Get Person
+ person = (
+ Person.query.execution_options(include_archived=True)
+ .filter_by(uuid=value.data.object.metadata.person_uuid)
+ .one()
+ )
+ # Get Subscription
+ subscription = (
+ Subscription.query.execution_options(include_archived=True)
+ .filter_by(stripe_subscription_id=value.data.object.id)
+ .one()
+ )
+ cancellation_date = value.data.object.canceled_at
+ cancellation_reason = value.data.object.cancellation_details.reason
+ cancellations.append(
+ {
+ "subscription": subscription,
+ "person": person,
+ "cancellation_date": cancellation_date,
+ "cancellation_reason": cancellation_reason,
+ }
+ )
+ return render_template(
+ "admin/recent_subscription_cancellations.html",
+ cancellations=cancellations,
+ )
+
+
@admin.route("/refresh-subscription-statuses")
def refresh_subscriptions():
update_stripe_subscription_statuses()
diff --git a/subscribie/blueprints/admin/stats.py b/subscribie/blueprints/admin/stats.py
index 82a7391e7..a75be6b37 100644
--- a/subscribie/blueprints/admin/stats.py
+++ b/subscribie/blueprints/admin/stats.py
@@ -1,6 +1,8 @@
from subscribie.database import database
from subscribie.models import Person, Subscription, Plan, PlanRequirements, Transaction
+from subscribie.utils import get_stripe_secret_key, get_stripe_connect_account_id
from sqlalchemy.sql import func
+import stripe
import logging
@@ -95,3 +97,16 @@ def get_number_of_transactions_with_donations():
.count()
)
return count
+
+
+def get_number_of_recent_subscription_cancellations():
+ stripe.api_key = get_stripe_secret_key()
+ connect_account_id = get_stripe_connect_account_id()
+
+ subscription_cancellations = stripe.Event.list(
+ stripe_account=connect_account_id,
+ limit=100,
+ types=["customer.subscription.deleted"],
+ )
+
+ return len(subscription_cancellations)
diff --git a/subscribie/blueprints/admin/templates/admin/dashboard.html b/subscribie/blueprints/admin/templates/admin/dashboard.html
index d68dd8a0b..c5f26a3b1 100644
--- a/subscribie/blueprints/admin/templates/admin/dashboard.html
+++ b/subscribie/blueprints/admin/templates/admin/dashboard.html
@@ -35,6 +35,7 @@
Checklist
Stats
You have: {{ num_active_subscribers }} subscribers with active subscriptions.
+
You've had: {{ num_recent_subscription_cancellations }} subscription cancellations in the last 30 days.
You've had: {{ num_subscribers }} subscribers since starting your shop.
You've had: {{ num_one_off_purchases }} people buy a one-off item from your shop.
You've had: {{ num_signups }} people either buy one-off or start a subscription{% if settings.donations_enabled %} or donations {% endif %} since starting your shop.
diff --git a/subscribie/blueprints/admin/templates/admin/recent_subscription_cancellations.html b/subscribie/blueprints/admin/templates/admin/recent_subscription_cancellations.html
new file mode 100644
index 000000000..2ad25e22c
--- /dev/null
+++ b/subscribie/blueprints/admin/templates/admin/recent_subscription_cancellations.html
@@ -0,0 +1,87 @@
+{% extends "admin/layout.html" %}
+{% block title %} Recent Subscription Cancellations{% endblock %}
+
+{% block body %}
+
+
Recent Subscription Cancellations
+
+
+
+
+
+
+
+ Below is the list of recent subscription cancellations (if any) within the last 30 days.
+
+
+ Be sure to click the subscriber name to investigate further, since they may have since signed-up to a new, or different plan.
+
+
+
Reason Code
+
+ - payment_failed - means all retry attempts have failed and the subscription is cancelled
+ - payment_disputed - means the subscriber disputed the charge(s) at their bank or card issuer
+ - cancellation_requested - means a cancellation was requested which caused the subscription to be cancelled. If plans have a "Cancel at" date set, they will natually cancel at the "Cancel at" date set on the plan
+
+
+
Please note this list only goes back 30 days
+
+
+
+
+
+
+
+
+{% endblock body %}