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") }}.

Click here to update billing information on Paddle, our payment partner:
- Update billing information + Update billing information +
+
+
+

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?
- + Cancel subscription 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"