From 9b91f4a4a467123b5552de22c7c765072fafbaaf Mon Sep 17 00:00:00 2001
From: Son NK <>
Date: Sun, 12 Apr 2020 19:43:07 +0200
Subject: [PATCH] support changing plan
---
.../templates/dashboard/billing.html | 33 ++++++++++++---
app/dashboard/views/billing.py | 42 +++++++++++++++++--
app/paddle_utils.py | 19 +++++++++
server.py | 33 +++++++++++++++
4 files changed, 118 insertions(+), 9 deletions(-)
diff --git a/app/dashboard/templates/dashboard/billing.html b/app/dashboard/templates/dashboard/billing.html
index a18af7e3..d2a74a17 100644
--- a/app/dashboard/templates/dashboard/billing.html
+++ b/app/dashboard/templates/dashboard/billing.html
@@ -14,8 +14,7 @@
{% if sub.cancelled %}
You are on the {{ sub.plan_name() }} plan.
- You have canceled your subscription and it will end on {{current_user.next_bill_date()}}
- ({{ sub.next_bill_date | dt }}).
+ You have canceled your subscription and it will end on {{ current_user.next_bill_date() }}
@@ -33,23 +32,47 @@
{% else %}
You are on the {{ sub.plan_name() }} plan. Thank you very much for supporting
- SimpleLogin. 🙌
+ SimpleLogin. 🙌
+ The next billing cycle starts at {{ sub.next_bill_date.strftime("%Y-%m-%d") }}.
+
+
+
Change Plan
+ You can change the plan at any moment.
+ Please note that the new billing cycle starts instantly
+ i.e. you will be charged immediately the annual fee when switching from monthly plan or vice-versa
+ without pro rata computation .
+
+ To change the plan you can also cancel the current one and subscribe a new one by the end of this plan.
+
+ {% if sub.plan == PlanEnum.yearly %}
+
+ {% else %}
+
+ {% endif %}
+
Cancel subscription
Don't want to protect your inbox anymore?
diff --git a/app/dashboard/views/billing.py b/app/dashboard/views/billing.py
index 9f8bc183..7c44e73d 100644
--- a/app/dashboard/views/billing.py
+++ b/app/dashboard/views/billing.py
@@ -1,11 +1,12 @@
from flask import render_template, flash, redirect, url_for, request
from flask_login import login_required, current_user
+from app.config import PADDLE_MONTHLY_PRODUCT_ID, PADDLE_YEARLY_PRODUCT_ID
from app.dashboard.base import dashboard_bp
from app.log import LOG
-from app.models import Subscription
+from app.models import Subscription, PlanEnum
from app.extensions import db
-from app.paddle_utils import cancel_subscription
+from app.paddle_utils import cancel_subscription, change_plan
@dashboard_bp.route("/billing", methods=["GET", "POST"])
@@ -29,10 +30,43 @@ def billing():
flash("Your subscription has been canceled successfully", "success")
else:
flash(
- "Something went wrong, sorry for the inconvenience. Please retry. We are already notified and will be on it asap",
+ "Something went wrong, sorry for the inconvenience. Please retry. "
+ "We are already notified and will be on it asap",
+ "error",
+ )
+
+ return redirect(url_for("dashboard.billing"))
+ elif request.form.get("form-name") == "change-monthly":
+ LOG.debug(f"User {current_user} changes to monthly plan")
+ success = change_plan(sub.subscription_id, PADDLE_MONTHLY_PRODUCT_ID)
+
+ if success:
+ sub.plan = PlanEnum.monthly
+ db.session.commit()
+ flash("Your subscription has been updated", "success")
+ else:
+ flash(
+ "Something went wrong, sorry for the inconvenience. Please retry. "
+ "We are already notified and will be on it asap",
+ "error",
+ )
+
+ return redirect(url_for("dashboard.billing"))
+ elif request.form.get("form-name") == "change-yearly":
+ LOG.debug(f"User {current_user} changes to yearly plan")
+ success = change_plan(sub.subscription_id, PADDLE_YEARLY_PRODUCT_ID)
+
+ if success:
+ sub.plan = PlanEnum.yearly
+ db.session.commit()
+ flash("Your subscription has been updated", "success")
+ else:
+ flash(
+ "Something went wrong, sorry for the inconvenience. Please retry. "
+ "We are already notified and will be on it asap",
"error",
)
return redirect(url_for("dashboard.billing"))
- return render_template("dashboard/billing.html", sub=sub)
+ return render_template("dashboard/billing.html", sub=sub, PlanEnum=PlanEnum)
diff --git a/app/paddle_utils.py b/app/paddle_utils.py
index 47740e11..cb782e90 100644
--- a/app/paddle_utils.py
+++ b/app/paddle_utils.py
@@ -76,3 +76,22 @@ def cancel_subscription(subscription_id: int) -> bool:
)
return res["success"]
+
+
+def change_plan(subscription_id: int, plan_id) -> bool:
+ r = requests.post(
+ "https://vendors.paddle.com/api/2.0/subscription/users/update",
+ data={
+ "vendor_id": PADDLE_VENDOR_ID,
+ "vendor_auth_code": PADDLE_AUTH_CODE,
+ "subscription_id": subscription_id,
+ "plan_id": plan_id,
+ },
+ )
+ res = r.json()
+ if not res["success"]:
+ LOG.error(
+ f"cannot change subscription {subscription_id} to {plan_id}, paddle response: {res}"
+ )
+
+ return res["success"]
diff --git a/server.py b/server.py
index bc6cdf99..e60d46b2 100644
--- a/server.py
+++ b/server.py
@@ -411,7 +411,40 @@ def setup_paddle_callback(app: Flask):
db.session.commit()
else:
return "No such subscription", 400
+ elif request.form.get("alert_name") == "subscription_updated":
+ subscription_id = request.form.get("subscription_id")
+ sub: Subscription = Subscription.get_by(subscription_id=subscription_id)
+ if sub:
+ LOG.debug(
+ "Update subscription %s %s on %s, next bill date %s",
+ subscription_id,
+ sub.user,
+ request.form.get("cancellation_effective_date"),
+ sub.next_bill_date,
+ )
+ if (
+ int(request.form.get("subscription_plan_id"))
+ == PADDLE_MONTHLY_PRODUCT_ID
+ ):
+ plan = PlanEnum.monthly
+ else:
+ plan = PlanEnum.yearly
+
+ sub.cancel_url = request.form.get("cancel_url")
+ sub.update_url = request.form.get("update_url")
+ sub.event_time = arrow.now()
+ sub.next_bill_date = arrow.get(
+ request.form.get("next_bill_date"), "YYYY-MM-DD"
+ ).date()
+ sub.plan = plan
+
+ # make sure to set the new plan as not-cancelled
+ sub.cancelled = False
+
+ db.session.commit()
+ else:
+ return "No such subscription", 400
return "OK"