diff --git a/app/dashboard/templates/dashboard/setting.html b/app/dashboard/templates/dashboard/setting.html index c2813bd1..01df04ae 100644 --- a/app/dashboard/templates/dashboard/setting.html +++ b/app/dashboard/templates/dashboard/setting.html @@ -145,20 +145,27 @@ - {% if current_user.get_subscription() %} -
-
-
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 %}