diff --git a/app/paddle_callback.py b/app/paddle_callback.py new file mode 100644 index 00000000..7d7402a7 --- /dev/null +++ b/app/paddle_callback.py @@ -0,0 +1,32 @@ +import arrow + +from app.db import Session +from app.email_utils import send_email, render +from app.log import LOG +from app.models import Subscription +from app import paddle_utils + + +def failed_payment(sub: Subscription, subscription_id: str): + LOG.w( + "Subscription failed payment %s for %s (sub %s)", + subscription_id, + sub.user, + sub.id, + ) + + sub.cancelled = True + Session.commit() + + user = sub.user + + paddle_utils.cancel_subscription(subscription_id) + + send_email( + user.email, + "SimpleLogin - your subscription has failed to be renewed", + render( + "transactional/subscription-cancel.txt", + end_date=arrow.arrow.datetime.utcnow(), + ), + ) diff --git a/server.py b/server.py index 5e183c58..f34e9f12 100644 --- a/server.py +++ b/server.py @@ -29,7 +29,7 @@ from sentry_sdk.integrations.flask import FlaskIntegration from sentry_sdk.integrations.sqlalchemy import SqlalchemyIntegration from werkzeug.middleware.proxy_fix import ProxyFix -from app import paddle_utils, config +from app import paddle_utils, config, paddle_callback from app.admin_model import ( SLAdminIndexView, UserAdmin, @@ -543,6 +543,11 @@ def setup_paddle_callback(app: Flask): sub: Subscription = Subscription.get_by(subscription_id=subscription_id) if sub: + next_bill_date = request.form.get("next_bill_date") + if not next_bill_date: + paddle_callback.failed_payment(sub, subscription_id) + return "OK" + LOG.d( "Update subscription %s %s on %s, next bill date %s", subscription_id, diff --git a/tests/test_paddle_callback.py b/tests/test_paddle_callback.py new file mode 100644 index 00000000..a81d8ce7 --- /dev/null +++ b/tests/test_paddle_callback.py @@ -0,0 +1,33 @@ +import arrow + +from app import paddle_callback +from app.db import Session +from app.mail_sender import mail_sender +from app.models import Subscription, PlanEnum +from tests.utils import create_new_user, random_token + + +@mail_sender.store_emails_test_decorator +def test_failed_payments(): + user = create_new_user() + paddle_sub_id = random_token() + sub = Subscription.create( + user_id=user.id, + cancel_url="https://checkout.paddle.com/subscription/cancel?user=1234", + update_url="https://checkout.paddle.com/subscription/update?user=1234", + subscription_id=paddle_sub_id, + event_time=arrow.now(), + next_bill_date=arrow.now().shift(days=10).date(), + plan=PlanEnum.monthly, + commit=True, + ) + Session.commit() + + paddle_callback.failed_payment(sub, paddle_sub_id) + + sub = Subscription.get_by(subscription_id=paddle_sub_id) + assert sub.cancelled + + assert 1 == len(mail_sender.get_stored_emails()) + mail_sent = mail_sender.get_stored_emails()[0] + assert mail_sent.envelope_to == user.email