Send automatic payment reminder emails, redesign mail settings

This commit is contained in:
Raphael Michel
2016-09-02 10:09:43 +02:00
parent 972ad211bf
commit a6bafd816b
7 changed files with 207 additions and 11 deletions

View File

@@ -0,0 +1,33 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2016-09-02 07:55
from __future__ import unicode_literals
from django.db import migrations, models
def preserve_event_settings(apps, schema_editor):
Event = apps.get_model('pretixbase', 'Event')
EventSetting = apps.get_model('pretixbase', 'EventSetting')
for e in Event.objects.all():
EventSetting.objects.create(object=e, key='mail_days_order_expire_warning', value='0')
class Migration(migrations.Migration):
dependencies = [
('pretixbase', '0035_merge'),
]
operations = [
migrations.AddField(
model_name='order',
name='expiry_reminder_sent',
field=models.BooleanField(default=False),
),
migrations.AlterField(
model_name='item',
name='allow_cancel',
field=models.BooleanField(default=True, help_text='If you deactivate this, an order including this product might not be cancelled by the user. It may still be cancelled by you.', verbose_name='Allow product to be cancelled'),
),
migrations.RunPython(preserve_event_settings)
]

View File

@@ -160,6 +160,9 @@ class Order(LoggedModel):
help_text=_("The text entered in this field will not be visible to the user and is available for your " help_text=_("The text entered in this field will not be visible to the user and is available for your "
"convenience.") "convenience.")
) )
expiry_reminder_sent = models.BooleanField(
default=False
)
class Meta: class Meta:
verbose_name = _("Order") verbose_name = _("Order")

View File

@@ -1,3 +1,4 @@
import logging
from collections import Counter, namedtuple from collections import Counter, namedtuple
from datetime import datetime, timedelta from datetime import datetime, timedelta
from decimal import Decimal from decimal import Decimal
@@ -6,6 +7,7 @@ from typing import List, Optional
from django.conf import settings from django.conf import settings
from django.db import transaction from django.db import transaction
from django.dispatch import receiver from django.dispatch import receiver
from django.utils.formats import date_format
from django.utils.timezone import now from django.utils.timezone import now
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
@@ -21,7 +23,7 @@ from pretix.base.payment import BasePaymentProvider
from pretix.base.services.invoices import ( from pretix.base.services.invoices import (
generate_cancellation, generate_invoice, invoice_qualified, generate_cancellation, generate_invoice, invoice_qualified,
) )
from pretix.base.services.mail import mail from pretix.base.services.mail import SendMailException, mail
from pretix.base.signals import ( from pretix.base.signals import (
order_paid, order_placed, periodic_task, register_payment_providers, order_paid, order_placed, periodic_task, register_payment_providers,
) )
@@ -48,6 +50,8 @@ error_messages = {
'voucher_required': _('You need a valid voucher code to order one of the products in your cart.'), 'voucher_required': _('You need a valid voucher code to order one of the products in your cart.'),
} }
logger = logging.getLogger(__name__)
def mark_order_paid(order: Order, provider: str=None, info: str=None, date: datetime=None, manual: bool=None, def mark_order_paid(order: Order, provider: str=None, info: str=None, date: datetime=None, manual: bool=None,
force: bool=False, send_mail: bool=True, user: User=None) -> Order: force: bool=False, send_mail: bool=True, user: User=None) -> Order:
@@ -368,6 +372,41 @@ def expire_orders(sender, **kwargs):
o.save() o.save()
@receiver(signal=periodic_task)
def send_expiry_warnings(sender, **kwargs):
eventcache = {}
today = now().date()
for o in Order.objects.filter(expires__gte=today, expiry_reminder_sent=False, status=Order.STATUS_PENDING).select_related('event'):
settings = eventcache.get(o.event.pk, None)
if settings is None:
settings = o.event.settings
eventcache[o.event.pk] = settings
days = settings.get('mail_days_order_expire_warning', as_type=int)
if days and (o.expires.date() - today).days <= days:
o.expiry_reminder_sent = True
o.save()
try:
mail(
o.email, _('Your is about to expire: %(code)s') % {'code': o.code},
settings.mail_text_order_expire_warning,
{
'event': o.event.name,
'url': build_absolute_uri(o.event, 'presale:event.order', kwargs={
'order': o.code,
'secret': o.secret
}),
'expire_date': date_format(o.expires, 'SHORT_DATE_FORMAT')
},
o.event, locale=o.locale
)
except SendMailException:
logger.exception('Reminder email could not be sent')
else:
o.log_action('pretix.event.order.expire_warning_sent')
class OrderChangeManager: class OrderChangeManager:
error_messages = { error_messages = {
'free_to_paid': _('You cannot change a free order to a paid order.'), 'free_to_paid': _('You cannot change a free order to a paid order.'),

View File

@@ -227,6 +227,24 @@ we successfully received your payment for {event}. Thank you!
You can change your order details and view the status of your order at You can change your order details and view the status of your order at
{url} {url}
Best regards,
Your {event} team"""))
},
'mail_days_order_expire_warning': {
'type': int,
'default': '3'
},
'mail_text_order_expire_warning': {
'type': LazyI18nString,
'default': LazyI18nString.from_gettext(ugettext_noop("""Hello,
we did not yet receive a payment for your order for {event}.
Please keep in mind that if we only guarantee your order if we receive
your payment before {expire_date}.
You can view the payment information and the status of your order at
{url}
Best regards, Best regards,
Your {event} team""")) Your {event} team"""))
}, },

View File

@@ -312,35 +312,54 @@ class MailSettingsForm(SettingsForm):
help_text=_("Sender address for outgoing emails") help_text=_("Sender address for outgoing emails")
) )
mail_text_order_placed = I18nFormField( mail_text_order_placed = I18nFormField(
label=_("Placed order"), label=_("Text"),
required=False, required=False,
widget=I18nTextarea, widget=I18nTextarea,
help_text=_("Available placeholders: {event}, {total}, {currency}, {date}, {paymentinfo}, {url}") help_text=_("Available placeholders: {event}, {total}, {currency}, {date}, {paymentinfo}, {url}")
) )
mail_text_order_paid = I18nFormField( mail_text_order_paid = I18nFormField(
label=_("Paid order"), label=_("Text"),
required=False, required=False,
widget=I18nTextarea, widget=I18nTextarea,
help_text=_("Available placeholders: {event}, {url}") help_text=_("Available placeholders: {event}, {url}")
) )
mail_text_order_free = I18nFormField( mail_text_order_free = I18nFormField(
label=_("Free order"), label=_("Text"),
required=False, required=False,
widget=I18nTextarea, widget=I18nTextarea,
help_text=_("Available placeholders: {event}, {url}") help_text=_("Available placeholders: {event}, {url}")
) )
mail_text_order_changed = I18nFormField( mail_text_order_changed = I18nFormField(
label=_("Changed order"), label=_("Text"),
required=False, required=False,
widget=I18nTextarea, widget=I18nTextarea,
help_text=_("Available placeholders: {event}, {url}") help_text=_("Available placeholders: {event}, {url}")
) )
mail_text_resend_link = I18nFormField( mail_text_resend_link = I18nFormField(
label=_("Resend link"), label=_("Text (sent by admin)"),
required=False, required=False,
widget=I18nTextarea, widget=I18nTextarea,
help_text=_("Available placeholders: {event}, {url}") help_text=_("Available placeholders: {event}, {url}")
) )
mail_text_resend_all_links = I18nFormField(
label=_("Text (requested by user)"),
required=False,
widget=I18nTextarea,
help_text=_("Available placeholders: {event}, {orders}")
)
mail_days_order_expire_warning = forms.IntegerField(
label=_("Number of days"),
required=False,
min_value=0,
help_text=_("This email will be sent out this many days before the order expires. If the "
"value is 0, the mail will never be sent.")
)
mail_text_order_expire_warning = I18nFormField(
label=_("Text"),
required=False,
widget=I18nTextarea,
help_text=_("Available placeholders: {event}, {url}, {expire_date}")
)
smtp_use_custom = forms.BooleanField( smtp_use_custom = forms.BooleanField(
label=_("Use custom SMTP server"), label=_("Use custom SMTP server"),
help_text=_("All mail related to your event will be sent over the smtp server specified by you."), help_text=_("All mail related to your event will be sent over the smtp server specified by you."),

View File

@@ -61,6 +61,7 @@ def pretixcontrol_logentry_display(sender: Event, logentry: LogEntry, **kwargs):
'pretix.event.order.comment': _('The order\'s internal comment has been updated.'), 'pretix.event.order.comment': _('The order\'s internal comment has been updated.'),
'pretix.event.order.contact.changed': _('The email address has been changed.'), 'pretix.event.order.contact.changed': _('The email address has been changed.'),
'pretix.event.order.payment.changed': _('The payment method has been changed.'), 'pretix.event.order.payment.changed': _('The payment method has been changed.'),
'pretix.event.order.expire_warning_sent': _('An email has been sent with a warning that the order is about to expire.'),
} }
if logentry.action_type in plains: if logentry.action_type in plains:
return plains[logentry.action_type] return plains[logentry.action_type]

View File

@@ -12,11 +12,94 @@
</fieldset> </fieldset>
<fieldset> <fieldset>
<legend>{% trans "E-mail content" %}</legend> <legend>{% trans "E-mail content" %}</legend>
{% bootstrap_field form.mail_text_order_placed layout="horizontal" %} <div class="panel-group" id="questions_group">
{% bootstrap_field form.mail_text_order_paid layout="horizontal" %} <div class="panel panel-default">
{% bootstrap_field form.mail_text_order_free layout="horizontal" %} <div class="panel-heading">
{% bootstrap_field form.mail_text_resend_link layout="horizontal" %} <h4 class="panel-title">
{% bootstrap_field form.mail_text_order_changed layout="horizontal" %} <a data-toggle="collapse" href="#order_placed">
<strong>{% trans "Placed order" %}</strong>
</a>
</h4>
</div>
<div id="order_placed" class="panel-collapse collapse">
<div class="panel-body">
{% bootstrap_field form.mail_text_order_placed layout="horizontal" %}
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" href="#order_paid">
<strong>{% trans "Paid order" %}</strong>
</a>
</h4>
</div>
<div id="order_paid" class="panel-collapse collapse">
<div class="panel-body">
{% bootstrap_field form.mail_text_order_paid layout="horizontal" %}
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" href="#order_free">
<strong>{% trans "Free order" %}</strong>
</a>
</h4>
</div>
<div id="order_free" class="panel-collapse collapse">
<div class="panel-body">
{% bootstrap_field form.mail_text_order_free layout="horizontal" %}
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" href="#resend_link">
<strong>{% trans "Resend link" %}</strong>
</a>
</h4>
</div>
<div id="resend_link" class="panel-collapse collapse">
<div class="panel-body">
{% bootstrap_field form.mail_text_resend_link layout="horizontal" %}
{% bootstrap_field form.mail_text_resend_all_links layout="horizontal" %}
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" href="#order_changed">
<strong>{% trans "Order changed" %}</strong>
</a>
</h4>
</div>
<div id="order_changed" class="panel-collapse collapse">
<div class="panel-body">
{% bootstrap_field form.mail_text_order_changed layout="horizontal" %}
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" href="#order_expirew">
<strong>{% trans "Payment reminder" %}</strong>
</a>
</h4>
</div>
<div id="order_expirew" class="panel-collapse collapse">
<div class="panel-body">
{% bootstrap_field form.mail_days_order_expire_warning layout="horizontal" %}
{% bootstrap_field form.mail_text_order_expire_warning layout="horizontal" %}
</div>
</div>
</div>
</div>
</fieldset> </fieldset>
<fieldset> <fieldset>
<legend>{% trans "SMTP settings" %}</legend> <legend>{% trans "SMTP settings" %}</legend>