diff --git a/app/app/urls.py b/app/app/urls.py index 94632f365e4..e6118502ede 100644 --- a/app/app/urls.py +++ b/app/app/urls.py @@ -540,6 +540,7 @@ path('_administration/email/wallpost', retail.emails.wallpost, name='wallpost_email'), path('_administration/email/grant_update', retail.emails.grant_update, name='grant_update_email'), path('_administration/email/grant_recontribute', retail.emails.grant_recontribute, name='grant_recontribute_email'), + path('_administration/email/grant_txn_failed', retail.emails.grant_txn_failed, name='grant_txn_failed_email'), path( '_administration/email/new_bounty_acceptance', retail.emails.new_bounty_acceptance, diff --git a/app/dashboard/management/commands/grant_txn_failed_email.py b/app/dashboard/management/commands/grant_txn_failed_email.py new file mode 100644 index 00000000000..cea356c3898 --- /dev/null +++ b/app/dashboard/management/commands/grant_txn_failed_email.py @@ -0,0 +1,32 @@ +''' + Copyright (C) 2019 Gitcoin Core + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +''' +from django.conf import settings +from django.core.management.base import BaseCommand + +from grants.models import Contribution +from marketing.mails import grant_txn_failed + + +class Command(BaseCommand): + help = 'sends emails for grant contributors whose contribution txns have failed' + + def handle(self, *args, **options): + if settings.DEBUG: + print("not active in non prod environments") + return + + failed_contribs = Contribution.objects.exclude(validator_passed=True) + + for failed_contrib in failed_contribs: + grant_txn_failed(failed_contrib.subscription.contributor_profile, failed_contrib.subscription.grant, failed_contrib.tx_id) diff --git a/app/marketing/mails.py b/app/marketing/mails.py index 99ba2fb662e..a36d3bdd938 100644 --- a/app/marketing/mails.py +++ b/app/marketing/mails.py @@ -36,7 +36,7 @@ render_bounty_expire_warning, render_bounty_feedback, render_bounty_request, render_bounty_startwork_expire_warning, render_bounty_unintersted, render_comment, render_faucet_rejected, render_faucet_request, render_featured_funded_bounty, render_funder_payout_reminder, render_funder_stale, render_gdpr_reconsent, - render_gdpr_update, render_grant_cancellation_email, render_grant_recontribute, render_grant_update, + render_gdpr_update, render_grant_cancellation_email, render_grant_recontribute, render_grant_txn_failed, render_grant_update, render_kudos_email, render_match_distribution, render_match_email, render_mention, render_new_bounty, render_new_bounty_acceptance, render_new_bounty_rejection, render_new_bounty_roundup, render_new_grant_email, render_new_supporter_email, render_new_work_submission, render_no_applicant_reminder, render_nth_day_email_campaign, @@ -288,6 +288,25 @@ def grant_cancellation(grant, subscription): finally: translation.activate(cur_language) +def grant_txn_failed(profile, grant, tx_id): + from_email = settings.CONTACT_EMAIL + to_email = profile.email + if not to_email: + if profile and profile.user: + to_email = profile.user.email + if not to_email: + return + + cur_language = translation.get_language() + + subject = f"Your Grant transaction failed. Wanna try again?" + + try: + setup_lang(to_email) + html, text = render_grant_txn_failed(to_email, grant, tx_id) + send_mail(from_email, to_email, subject, text, html, categories=['transactional', func_name()]) + finally: + translation.activate(cur_language) def subscription_terminated(grant, subscription): if subscription and subscription.negative: diff --git a/app/retail/emails.py b/app/retail/emails.py index 70b8ef49888..3b7f70db64a 100644 --- a/app/retail/emails.py +++ b/app/retail/emails.py @@ -69,6 +69,7 @@ ('comment', _('Comment Emails'), _('Only when you are sent a comment')), ('wall_post', _('Wall Post Emails'), _('Only when someone writes on your wall')), ('grant_updates', _('Grant Update Emails'), _('Updates from Grant Owners about grants you\'ve funded.')), + ('grant_txn_failed', _('Grant Transaction Failed Emails'), _('Notifies Grant contributors when their contribution txn has failed.')), ] @@ -887,6 +888,24 @@ def render_grant_recontribute(to_email, prev_round_start=(2020, 3, 23), prev_rou return response_html, response_txt +def render_grant_txn_failed(to_email, grant, tx_id): + email_style = 27 + + params = { + 'id': grant.id, + 'grant_title': grant.title, + 'tx_id': tx_id, + 'tx_url': "https://etherscan.io/tx/"+tx_id, + 'bulk_add_url': "https://gitcoin.co/grants/cart/bulk-add/" + str(grant.id), + 'email_style': email_style, + 'hide_bottom_logo': True, + } + + response_html = premailer_transform(render_to_string("emails/grant_txn_failed.html", params)) + response_txt = render_to_string("emails/grant_txn_failed.txt", params) + + return response_html, response_txt + def render_wallpost(to_email, activity): params = { 'activity': activity, @@ -1375,6 +1394,11 @@ def grant_update(request): def grant_recontribute(request): response_html, _ = render_grant_recontribute(settings.CONTACT_EMAIL) return HttpResponse(response_html) + +def grant_txn_failed(request): + failed_contrib = Contribution.objects.filter(subscription__contributor_profile__user__email=settings.CONTACT_EMAIL).exclude(validator_passed=True).first() + response_html, _ = render_grant_txn_failed(settings.CONTACT_EMAIL, failed_contrib.subscription.grant, failed_contrib.tx_id) + return HttpResponse(response_html) @staff_member_required def wallpost(request): diff --git a/app/retail/templates/emails/grant_txn_failed.html b/app/retail/templates/emails/grant_txn_failed.html new file mode 100644 index 00000000000..be3b3d837c5 --- /dev/null +++ b/app/retail/templates/emails/grant_txn_failed.html @@ -0,0 +1,59 @@ +{% extends 'emails/template.html' %} +{% comment %} + Copyright (C) 2018 Gitcoin Core + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +{% endcomment %} +{% load i18n humanize %} + +{% block content %} + + + +

Grants transaction failed, try again?

+ +
+

+ Your contribution to {{ grant_title }} failed due to a technical issue. +

+
+ +
+

+ Transaction ID:
+ {{ tx_id }} +

+
+ +
+ {% trans "Resubmit Transaction" %} +
+ View on Etherscan +
+ +{% endblock %} \ No newline at end of file diff --git a/app/retail/templates/emails/grant_txn_failed.txt b/app/retail/templates/emails/grant_txn_failed.txt new file mode 100644 index 00000000000..8a0f1241089 --- /dev/null +++ b/app/retail/templates/emails/grant_txn_failed.txt @@ -0,0 +1,7 @@ +Grants transaction failed, try again? + + Your contribution to {{ grant_title }} failed due to a technical issue. + + Transaction ID: {{ tx_id }} + + View on Etherscan \ No newline at end of file diff --git a/app/retail/templates/emails/template.html b/app/retail/templates/emails/template.html index fb85fb71768..78288e1f1f7 100644 --- a/app/retail/templates/emails/template.html +++ b/app/retail/templates/emails/template.html @@ -323,6 +323,18 @@ } } + @media (min-width: 768px) { + .grant-txn-msg { + width: 70%; + } + } + + @media (min-width: 1200px) { + .grant-txn-msg { + width: 60%; + } + } + /* Bootstrap styles used for activities -- START */ *,