From 1973db13306ffec3d48288e04b403198d97cdb9e Mon Sep 17 00:00:00 2001 From: Will O'Beirne Date: Wed, 6 Mar 2019 15:33:09 -0500 Subject: [PATCH 1/2] Notify user if contribution expires. --- admin/src/components/Emails/emails.ts | 5 +++ backend/grant/admin/example_emails.py | 7 ++++ backend/grant/email/send.py | 13 ++++++ backend/grant/proposal/models.py | 5 ++- backend/grant/task/jobs.py | 42 ++++++++++++++++++- .../emails/contribution_expired.html | 22 ++++++++++ .../templates/emails/contribution_expired.txt | 13 ++++++ 7 files changed, 105 insertions(+), 2 deletions(-) create mode 100644 backend/grant/templates/emails/contribution_expired.html create mode 100644 backend/grant/templates/emails/contribution_expired.txt diff --git a/admin/src/components/Emails/emails.ts b/admin/src/components/Emails/emails.ts index 80752ea9..fdb2e2a7 100644 --- a/admin/src/components/Emails/emails.ts +++ b/admin/src/components/Emails/emails.ts @@ -92,6 +92,11 @@ export default [ title: 'Contribution proposal canceled', description: 'Sent to contributors when an admin cancels the proposal after funding', }, + { + id: 'contribution_expired', + title: 'Contribution expired', + description: 'Sent 24 hours after a contribution is made with no confirmation', + }, { id: 'comment_reply', title: 'Comment reply', diff --git a/backend/grant/admin/example_emails.py b/backend/grant/admin/example_emails.py index 508a3072..076d034a 100644 --- a/backend/grant/admin/example_emails.py +++ b/backend/grant/admin/example_emails.py @@ -126,6 +126,13 @@ example_email_args = { 'refund_address': 'ztqdzvnK2SE27FCWg69EdissCBn7twnfd1XWLrftiZaT4rSFCkp7eQGQDSWXBF43sM5cyA4c8qyVjBP9Cf4zTcFJxf71ve8', 'account_settings_url': 'http://accountsettingsurl.com/', }, + 'contribution_expired': { + 'proposal': proposal, + 'contribution': contribution, + 'contact_url': 'http://somecontacturl.com', + 'profile_url': 'http://someprofile.com', + 'proposal_url': 'http://someproposal.com', + }, 'comment_reply': { 'author': user, 'proposal': proposal, diff --git a/backend/grant/email/send.py b/backend/grant/email/send.py index 1fc7fbf2..d56b79ff 100644 --- a/backend/grant/email/send.py +++ b/backend/grant/email/send.py @@ -199,6 +199,18 @@ def contribution_proposal_canceled(email_args): } +def contribution_expired(email_args): + return { + 'subject': 'Your contribution expired', + 'title': 'Contribution expired', + 'preview': 'Your {} ZEC contribution to {} could not be confirmed, and has expired'.format( + email_args['contribution'].amount, + email_args['proposal'].title, + ), + 'subscription': EmailSubscription.FUNDED_PROPOSAL_CONTRIBUTION, + } + + def comment_reply(email_args): return { 'subject': 'New reply from {}'.format(email_args['author'].display_name), @@ -282,6 +294,7 @@ get_info_lookup = { 'contribution_refunded': contribution_refunded, 'contribution_proposal_failed': contribution_proposal_failed, 'contribution_proposal_canceled': contribution_proposal_canceled, + 'contribution_expired': contribution_expired, 'comment_reply': comment_reply, 'proposal_arbiter': proposal_arbiter, 'milestone_request': milestone_request, diff --git a/backend/grant/proposal/models.py b/backend/grant/proposal/models.py index e1b43e5d..a0062812 100644 --- a/backend/grant/proposal/models.py +++ b/backend/grant/proposal/models.py @@ -21,6 +21,7 @@ from grant.utils.enums import ( MilestoneStage ) from grant.utils.stubs import anonymous_user +from grant.task.jobs import ContributionExpired proposal_team = db.Table( 'proposal_team', db.Model.metadata, @@ -351,7 +352,9 @@ class Proposal(db.Model): staking=staking, ) db.session.add(contribution) - db.session.commit() + db.session.flush() + task = ContributionExpired(contribution) + task.make_task() return contribution def get_staking_contribution(self, user_id: int): diff --git a/backend/grant/task/jobs.py b/backend/grant/task/jobs.py index 82a23a16..e7d17b89 100644 --- a/backend/grant/task/jobs.py +++ b/backend/grant/task/jobs.py @@ -2,7 +2,7 @@ from datetime import datetime, timedelta from grant.extensions import db from grant.email.send import send_email -from grant.utils.enums import ProposalStage +from grant.utils.enums import ProposalStage, ContributionStatus from grant.utils.misc import make_url @@ -86,8 +86,48 @@ class ProposalDeadline: 'account_settings_url': make_url('/profile/settings?tab=account') }) +class ContributionExpired: + JOB_TYPE = 3 + + def __init__(self, contribution): + self.contribution = contribution + + def blobify(self): + return { + "contribution_id": self.contribution.id, + } + + def make_task(self): + from .models import Task + task = Task( + job_type=self.JOB_TYPE, + blob=self.blobify(), + execute_after=self.contribution.date_created + timedelta(hours=24), + ) + db.session.add(task) + db.session.commit() + + @staticmethod + def process_task(task): + from grant.proposal.models import ProposalContribution + contribution = ProposalContribution.query.filter_by(id=task.blob["contribution_id"]).first() + + # If it's missing or not pending, noop out + if not contribution or contribution.status != ContributionStatus.PENDING: + return + + # Otherwise, inform the user (if not anonymous) + if contribution.user: + send_email(contribution.user.email_address, 'contribution_expired', { + 'contribution': contribution, + 'proposal': contribution.proposal, + 'contact_url': make_url('/contact'), + 'profile_url': make_url(f'/profile/{contribution.user.id}'), + 'proposal_url': make_url(f'/proposals/{contribution.proposal.id}'), + }) JOBS = { 1: ProposalReminder.process_task, 2: ProposalDeadline.process_task, + 3: ContributionExpired.process_task, } diff --git a/backend/grant/templates/emails/contribution_expired.html b/backend/grant/templates/emails/contribution_expired.html new file mode 100644 index 00000000..86c6b52f --- /dev/null +++ b/backend/grant/templates/emails/contribution_expired.html @@ -0,0 +1,22 @@ +

+ Your {{ args.contribution.amount }} ZEC contribution to + {{ args.proposal.title}} could not be confirmed on-chain, + and has expired. +

+ +

+ If you did not send the contribution: + You have nothing to worry about, you can simply delete the contribution from + your profile. Just make that sure you do + not send any money to the contribution address. If you'd still like to make a + contribution, you can start a new one on the + proposal page. +

+ +

+ If you're sure you sent the contribution: + Please contact our team using a method from the + contact page with details about the + contribution, such as a transaction ID or payment + disclosure from the transaction. +

diff --git a/backend/grant/templates/emails/contribution_expired.txt b/backend/grant/templates/emails/contribution_expired.txt new file mode 100644 index 00000000..4ceaae0e --- /dev/null +++ b/backend/grant/templates/emails/contribution_expired.txt @@ -0,0 +1,13 @@ +Your {{ args.contribution.amount }} ZEC contribution to "{{ args.proposal.title}}" +could not be confirmed on-chain, and has expired. + +If you did not send the contribution, you have nothing to worry about, you can +simply delete the contribution from your profile. Just make that sure you do not +send any money to the contribution address. If you'd still like to make a +contribution, you can start a new one on the proposal page. + +If you're sure you've sent the contribution, please contact our team using a method +from the link below with details about the contribution, such as a transaction ID or +payment disclosure from the transaction. + +Contact us at {{ args.contact_url }} \ No newline at end of file From a9ae24557803edeb28a66054c879fd3ed76136b1 Mon Sep 17 00:00:00 2001 From: Will O'Beirne Date: Wed, 6 Mar 2019 16:39:30 -0500 Subject: [PATCH 2/2] Dont add task for anon contributions --- backend/grant/proposal/models.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/backend/grant/proposal/models.py b/backend/grant/proposal/models.py index a0062812..41943a6c 100644 --- a/backend/grant/proposal/models.py +++ b/backend/grant/proposal/models.py @@ -353,8 +353,10 @@ class Proposal(db.Model): ) db.session.add(contribution) db.session.flush() - task = ContributionExpired(contribution) - task.make_task() + if user_id: + task = ContributionExpired(contribution) + task.make_task() + db.session.commit() return contribution def get_staking_contribution(self, user_id: int):