-
Billing
-
- Manage your current subscription.
-
-
+
+
+
Current Plan
+
+ {% if current_user.get_subscription() %}
+ You are on the {{ current_user.get_subscription().plan_name() }} plan.
- Manage Billing
+ Manage Subscription
-
+ {% elif manual_sub %}
+ You are on the Premium plan. The plan ends {{ manual_sub.end_at | dt }}.
+ {% elif current_user.lifetime %}
+ You have the lifetime licence.
+ {% elif current_user.in_trial() %}
+ You are in the trial period. The trial ends {{ current_user.trial_end | dt }}.
+ {% else %}
+ You are on the Free plan.
+ {% endif %}
- {% endif %}
+
diff --git a/app/dashboard/views/setting.py b/app/dashboard/views/setting.py
index 632b7bd1..22f0fdd4 100644
--- a/app/dashboard/views/setting.py
+++ b/app/dashboard/views/setting.py
@@ -26,6 +26,7 @@ from app.models import (
CustomDomain,
Client,
AliasGeneratorEnum,
+ ManualSubscription,
)
from app.utils import random_string
@@ -183,6 +184,7 @@ def setting():
headers={"Content-Disposition": "attachment;filename=data.json"},
)
+ manual_sub = ManualSubscription.get_by(user_id=current_user.id)
return render_template(
"dashboard/setting.html",
form=form,
@@ -191,6 +193,7 @@ def setting():
change_email_form=change_email_form,
pending_email=pending_email,
AliasGeneratorEnum=AliasGeneratorEnum,
+ manual_sub=manual_sub,
)
diff --git a/app/models.py b/app/models.py
index d6c36329..4d71fa11 100644
--- a/app/models.py
+++ b/app/models.py
@@ -194,6 +194,10 @@ class User(db.Model, ModelMixin, UserMixin):
if sub:
return True
+ manual_sub: ManualSubscription = ManualSubscription.get_by(user_id=self.id)
+ if manual_sub and manual_sub.end_at > arrow.now():
+ return True
+
return False
def in_trial(self):
@@ -736,6 +740,24 @@ class Subscription(db.Model, ModelMixin):
return "Yearly ($29.99/year)"
+class ManualSubscription(db.Model, ModelMixin):
+ """
+ For users who use other forms of payment and therefore not pass by Paddle
+ """
+
+ user_id = db.Column(
+ db.ForeignKey(User.id, ondelete="cascade"), nullable=False, unique=True
+ )
+
+ # an reminder is sent several days before the subscription ends
+ end_at = db.Column(ArrowType, nullable=False)
+
+ # for storing note about this subscription
+ comment = db.Column(db.Text, nullable=True)
+
+ user = db.relationship(User)
+
+
class DeletedAlias(db.Model, ModelMixin):
"""Store all deleted alias to make sure they are NOT reused"""
diff --git a/cron.py b/cron.py
index c7e0175f..62d850f3 100644
--- a/cron.py
+++ b/cron.py
@@ -3,7 +3,7 @@ import argparse
import arrow
from app.config import IGNORED_EMAILS, ADMIN_EMAIL
-from app.email_utils import send_email, send_trial_end_soon_email
+from app.email_utils import send_email, send_trial_end_soon_email, render
from app.extensions import db
from app.log import LOG
from app.models import (
@@ -14,6 +14,7 @@ from app.models import (
ForwardEmail,
CustomDomain,
Client,
+ ManualSubscription,
)
from server import create_app
@@ -29,6 +30,35 @@ def notify_trial_end():
send_trial_end_soon_email(user)
+def notify_manual_sub_end():
+ for manual_sub in ManualSubscription.query.all():
+ need_reminder = False
+ if arrow.now().shift(days=14) > manual_sub.end_at > arrow.now().shift(days=13):
+ need_reminder = True
+ elif arrow.now().shift(days=4) > manual_sub.end_at > arrow.now().shift(days=3):
+ need_reminder = True
+
+ if need_reminder:
+ user = manual_sub.user
+ LOG.debug("Remind user %s that their manual sub is ending soon", user)
+ send_email(
+ user.email,
+ f"Your trial will end soon {user.name}",
+ render(
+ "transactional/manual-subscription-end.txt",
+ name=user.name,
+ user=user,
+ manual_sub=manual_sub,
+ ),
+ render(
+ "transactional/manual-subscription-end.html",
+ name=user.name,
+ user=user,
+ manual_sub=manual_sub,
+ ),
+ )
+
+
def stats():
"""send admin stats everyday"""
if not ADMIN_EMAIL:
@@ -118,7 +148,7 @@ if __name__ == "__main__":
"--job",
help="Choose a cron job to run",
type=str,
- choices=["stats", "notify_trial_end",],
+ choices=["stats", "notify_trial_end", "notify_manual_subscription_end"],
)
args = parser.parse_args()
@@ -131,3 +161,6 @@ if __name__ == "__main__":
elif args.job == "notify_trial_end":
LOG.d("Notify users with trial ending soon")
notify_trial_end()
+ elif args.job == "notify_manual_subscription_end":
+ LOG.d("Notify users with manual subscription ending soon")
+ notify_manual_sub_end()
diff --git a/crontab.yml b/crontab.yml
index 373138ae..fae823b4 100644
--- a/crontab.yml
+++ b/crontab.yml
@@ -10,3 +10,9 @@ jobs:
shell: /bin/bash
schedule: "0 8 * * *"
captureStderr: true
+
+ - name: SimpleLogin Notify Manual Subscription Ends
+ command: python /code/cron.py -j notify_manual_subscription_end
+ shell: /bin/bash
+ schedule: "0 9 * * *"
+ captureStderr: true
diff --git a/migrations/versions/2020_022316_e3cb44b953f2_.py b/migrations/versions/2020_022316_e3cb44b953f2_.py
new file mode 100644
index 00000000..4b8cf026
--- /dev/null
+++ b/migrations/versions/2020_022316_e3cb44b953f2_.py
@@ -0,0 +1,39 @@
+"""empty message
+
+Revision ID: e3cb44b953f2
+Revises: f580030d9beb
+Create Date: 2020-02-23 16:43:45.843338
+
+"""
+import sqlalchemy_utils
+from alembic import op
+import sqlalchemy as sa
+
+
+# revision identifiers, used by Alembic.
+revision = 'e3cb44b953f2'
+down_revision = 'f580030d9beb'
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.create_table('manual_subscription',
+ sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
+ sa.Column('created_at', sqlalchemy_utils.types.arrow.ArrowType(), nullable=False),
+ sa.Column('updated_at', sqlalchemy_utils.types.arrow.ArrowType(), nullable=True),
+ sa.Column('user_id', sa.Integer(), nullable=False),
+ sa.Column('end_at', sqlalchemy_utils.types.arrow.ArrowType(), nullable=False),
+ sa.Column('comment', sa.Text(), nullable=True),
+ sa.ForeignKeyConstraint(['user_id'], ['users.id'], ondelete='cascade'),
+ sa.PrimaryKeyConstraint('id'),
+ sa.UniqueConstraint('user_id')
+ )
+ # ### end Alembic commands ###
+
+
+def downgrade():
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.drop_table('manual_subscription')
+ # ### end Alembic commands ###
diff --git a/templates/emails/transactional/manual-subscription-end.html b/templates/emails/transactional/manual-subscription-end.html
new file mode 100644
index 00000000..724f6a90
--- /dev/null
+++ b/templates/emails/transactional/manual-subscription-end.html
@@ -0,0 +1,26 @@
+{% extends "base.html" %}
+
+{% block content %}
+ {% if name %}
+ {{ render_text("Hi " + name + ",") }}
+ {% else %}
+ {{ render_text("Hi,") }}
+ {% endif %}
+
+ {{ render_text("Your subscription will end " + manual_sub.end_at.humanize() + ".") }}
+
+ {{ render_text("When the subscription ends:") }}
+
+ {{ render_text("- All aliases/domains/directories you have created are kept and continue working normally.") }}
+ {{ render_text("- You cannot create new aliases if you exceed the free plan limit, i.e. have more than 5 aliases.") }}
+ {{ render_text("- As features like catch-all or directory allow you to create aliases on-the-fly, those aliases cannot be automatically created if you have more than 5 aliases.") }}
+ {{ render_text("- You cannot add new domain or directory.") }}
+
+ {{ render_text('You can upgrade today to continue using all these Premium features (and much more coming).') }}
+
+ {{ render_text('Thanks,
SimpleLogin Team.') }}
+ {{ render_text('P.S. If you have any questions or need any help, please don\'t hesitate to reach out. You can simply reply to this email or reach us via Twitter/Github.') }}
+
+
+{% endblock %}
+
diff --git a/templates/emails/transactional/manual-subscription-end.txt b/templates/emails/transactional/manual-subscription-end.txt
new file mode 100644
index 00000000..abfc69a6
--- /dev/null
+++ b/templates/emails/transactional/manual-subscription-end.txt
@@ -0,0 +1,15 @@
+Hi {{name}}
+
+Your subscription will end {{ manual_sub.end_at.humanize() }}.
+
+When the subscription ends:
+
+- All aliases/domains/directories you have created are kept and continue working.
+- You cannot create new aliases if you exceed the free plan limit, i.e. have more than 5 aliases.
+- As features like "catch-all" or "directory" allow you to create aliases on-the-fly, those aliases cannot be automatically created if you have more than 5 aliases.
+- You cannot add new domain or directory.
+
+You can upgrade today to continue using all these Premium features (and much more coming).
+
+Best,
+Son - SimpleLogin founder.
diff --git a/templates/emails/transactional/trial-end.html b/templates/emails/transactional/trial-end.html
index 2303617b..a03092b2 100644
--- a/templates/emails/transactional/trial-end.html
+++ b/templates/emails/transactional/trial-end.html
@@ -37,6 +37,7 @@
requires an investment of your time, and we appreciate you giving us a chance.') }}
{{ render_text('Thanks,
SimpleLogin Team.') }}
{{ render_text('P.S. If you have any questions or need any help, please don\'t hesitate to reach out. You can simply reply to this email or reach us via Twitter/Github.') }}
+ {{ raw_url("https://app.simplelogin.io/dashboard/pricing") }}
{% endblock %}