mirror of
https://github.com/simple-login/app.git
synced 2024-09-28 20:51:29 +02:00
Merge pull request #126 from simple-login/change-plan
User can change plan
This commit is contained in:
commit
b4f28a5156
@ -15,7 +15,6 @@
|
||||
<p>
|
||||
You are on the <b>{{ sub.plan_name() }}</b> plan. <br>
|
||||
You have canceled your subscription and it will end on {{ current_user.next_bill_date() }}
|
||||
({{ sub.next_bill_date | dt }}).
|
||||
</p>
|
||||
|
||||
<hr>
|
||||
@ -33,23 +32,47 @@
|
||||
{% else %}
|
||||
<p>
|
||||
You are on the <b>{{ sub.plan_name() }}</b> plan. Thank you very much for supporting
|
||||
SimpleLogin. 🙌
|
||||
SimpleLogin. 🙌 <br>
|
||||
The next billing cycle starts at {{ sub.next_bill_date.strftime("%Y-%m-%d") }}.
|
||||
</p>
|
||||
|
||||
<div class="mt-3">
|
||||
Click here to update billing information on Paddle, our payment partner: <br>
|
||||
<a class="btn btn-success" href="{{ sub.update_url }}"> Update billing information </a>
|
||||
<a class="btn btn-outline-success mt-2" href="{{ sub.update_url }}"> Update billing information </a>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="mt-6">
|
||||
<h4>Change Plan</h4>
|
||||
You can change the plan at any moment. <br>
|
||||
Please note that the new billing cycle starts instantly
|
||||
i.e. you will be charged <b>immediately</b> the annual fee when switching from monthly plan or vice-versa
|
||||
<b>without pro rata computation </b>. <br>
|
||||
|
||||
To change the plan you can also cancel the current one and subscribe a new one <b>by the end</b> of this plan.
|
||||
|
||||
{% if sub.plan == PlanEnum.yearly %}
|
||||
<form method="post">
|
||||
<input type="hidden" name="form-name" value="change-monthly">
|
||||
<button class="btn btn-outline-primary mt-2">Change to Monthly Plan</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<form method="post">
|
||||
<input type="hidden" name="form-name" value="change-yearly">
|
||||
<button class="btn btn-outline-primary mt-2">Change to Yearly Plan</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<div>
|
||||
<h4>Cancel subscription</h4>
|
||||
Don't want to protect your inbox anymore? <br>
|
||||
|
||||
<form method="post">
|
||||
<input type="hidden" name="form-name" value="cancel">
|
||||
|
||||
<span class="cancel btn btn-warning">
|
||||
<span class="cancel btn btn-outline-danger mt-2">
|
||||
Cancel subscription <i class="fe fe-alert-triangle text-danger"></i>
|
||||
</span>
|
||||
</form>
|
||||
|
@ -27,7 +27,7 @@
|
||||
|
||||
<div class="card-body">
|
||||
<div class="card-title">
|
||||
Change Email Address
|
||||
Email Address
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Email</label>
|
||||
@ -60,9 +60,12 @@
|
||||
|
||||
<div class="card-body">
|
||||
<div class="card-title">
|
||||
Change Profile
|
||||
Profile
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div>
|
||||
These informations will be filled up automatically when you use "Sign in with SimpleLogin" button
|
||||
</div>
|
||||
<div class="form-group mt-3">
|
||||
<label class="form-label">Name</label>
|
||||
{{ form.name(class="form-control", value=current_user.name) }}
|
||||
{{ render_field_errors(form.name) }}
|
||||
|
@ -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)
|
||||
|
@ -202,7 +202,7 @@ class User(db.Model, ModelMixin, UserMixin):
|
||||
|
||||
return user
|
||||
|
||||
def lifetime_or_active_subscription(self) -> bool:
|
||||
def _lifetime_or_active_subscription(self) -> bool:
|
||||
"""True if user has lifetime licence or active subscription"""
|
||||
if self.lifetime:
|
||||
return True
|
||||
@ -219,7 +219,7 @@ class User(db.Model, ModelMixin, UserMixin):
|
||||
|
||||
def in_trial(self):
|
||||
"""return True if user does not have lifetime licence or an active subscription AND is in trial period"""
|
||||
if self.lifetime_or_active_subscription():
|
||||
if self._lifetime_or_active_subscription():
|
||||
return False
|
||||
|
||||
if self.trial_end and arrow.now() < self.trial_end:
|
||||
@ -228,7 +228,7 @@ class User(db.Model, ModelMixin, UserMixin):
|
||||
return False
|
||||
|
||||
def should_upgrade(self):
|
||||
if self.lifetime_or_active_subscription():
|
||||
if self._lifetime_or_active_subscription():
|
||||
# user who has canceled can also re-subscribe
|
||||
sub: Subscription = self.get_subscription()
|
||||
if sub and sub.cancelled:
|
||||
@ -264,7 +264,7 @@ class User(db.Model, ModelMixin, UserMixin):
|
||||
- in trial period or
|
||||
- active subscription
|
||||
"""
|
||||
if self.lifetime_or_active_subscription():
|
||||
if self._lifetime_or_active_subscription():
|
||||
return True
|
||||
|
||||
if self.trial_end and arrow.now() < self.trial_end:
|
||||
|
@ -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"]
|
||||
|
37
server.py
37
server.py
@ -133,6 +133,7 @@ def fake_data():
|
||||
otp_secret="base32secret3232",
|
||||
)
|
||||
db.session.commit()
|
||||
user.trial_end = None
|
||||
|
||||
LifetimeCoupon.create(code="coupon", nb_used=10)
|
||||
db.session.commit()
|
||||
@ -385,6 +386,9 @@ def setup_paddle_callback(app: Flask):
|
||||
LOG.debug("Update subscription %s", subscription_id)
|
||||
|
||||
sub: Subscription = Subscription.get_by(subscription_id=subscription_id)
|
||||
# when user subscribes, the "subscription_payment_succeeded" can arrive BEFORE "subscription_created"
|
||||
# at that time, subscription object does not exist yet
|
||||
if sub:
|
||||
sub.event_time = arrow.now()
|
||||
sub.next_bill_date = arrow.get(
|
||||
request.form.get("next_bill_date"), "YYYY-MM-DD"
|
||||
@ -411,7 +415,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"
|
||||
|
||||
|
||||
|
@ -70,3 +70,16 @@ em {
|
||||
.cursor {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/*Left border for alert zone*/
|
||||
.alert-primary{
|
||||
border-left: 5px #467fcf solid;
|
||||
}
|
||||
|
||||
.alert-danger{
|
||||
border-left: 5px #6b1110 solid;
|
||||
}
|
||||
|
||||
.alert-danger::before {
|
||||
content: "⚠️";
|
||||
}
|
@ -27,7 +27,7 @@
|
||||
|
||||
{% if current_user.in_trial() %}
|
||||
<small class="text-success d-block mt-1">Trial ends {{ current_user.trial_end|dt }}</small>
|
||||
{% elif current_user.lifetime_or_active_subscription() %}
|
||||
{% elif current_user.is_premium() %}
|
||||
<small class="text-success d-block mt-1">Premium
|
||||
{% if current_user.is_cancel() %}
|
||||
until {{ current_user.next_bill_date() }}
|
||||
|
Loading…
Reference in New Issue
Block a user