diff --git a/doc/api/resources/invoices.rst b/doc/api/resources/invoices.rst
index e8ec492408..85d11943c6 100644
--- a/doc/api/resources/invoices.rst
+++ b/doc/api/resources/invoices.rst
@@ -42,6 +42,7 @@ introductory_text string Text to be prin
additional_text string Text to be printed below the product list
payment_provider_text string Text to be printed below the product list with
payment information
+payment_provider_stamp string Short text to be visibly printed to indicate payment status
footer_text string Text to be printed in the page footer area
lines list of objects The actual invoice contents
├ position integer Number of the line within an invoice.
@@ -178,6 +179,7 @@ Endpoints
"internal_reference": "",
"additional_text": "We are looking forward to see you on our conference!",
"payment_provider_text": "Please transfer the money to our account ABC…",
+ "payment_provider_stamp": null,
"footer_text": "Big Events LLC - Registration No. 123456 - VAT ID: EU0987654321",
"lines": [
{
@@ -268,6 +270,7 @@ Endpoints
"internal_reference": "",
"additional_text": "We are looking forward to see you on our conference!",
"payment_provider_text": "Please transfer the money to our account ABC…",
+ "payment_provider_stamp": null,
"footer_text": "Big Events LLC - Registration No. 123456 - VAT ID: EU0987654321",
"lines": [
{
diff --git a/doc/development/api/payment.rst b/doc/development/api/payment.rst
index a29b0ce6bd..65da00d3bd 100644
--- a/doc/development/api/payment.rst
+++ b/doc/development/api/payment.rst
@@ -102,6 +102,8 @@ The provider class
.. automethod:: render_invoice_text
+ .. automethod:: render_invoice_stamp
+
.. automethod:: order_change_allowed
.. automethod:: payment_prepare
diff --git a/src/pretix/api/serializers/order.py b/src/pretix/api/serializers/order.py
index b44a74b9c4..794e745d2a 100644
--- a/src/pretix/api/serializers/order.py
+++ b/src/pretix/api/serializers/order.py
@@ -1484,9 +1484,9 @@ class InvoiceSerializer(I18nAwareModelSerializer):
'invoice_to', 'invoice_to_company', 'invoice_to_name', 'invoice_to_street', 'invoice_to_zipcode',
'invoice_to_city', 'invoice_to_state', 'invoice_to_country', 'invoice_to_vat_id', 'invoice_to_beneficiary',
'custom_field', 'date', 'refers', 'locale',
- 'introductory_text', 'additional_text', 'payment_provider_text', 'footer_text', 'lines',
- 'foreign_currency_display', 'foreign_currency_rate', 'foreign_currency_rate_date',
- 'internal_reference')
+ 'introductory_text', 'additional_text', 'payment_provider_text', 'payment_provider_stamp',
+ 'footer_text', 'lines', 'foreign_currency_display', 'foreign_currency_rate',
+ 'foreign_currency_rate_date', 'internal_reference')
class OrderPaymentCreateSerializer(I18nAwareModelSerializer):
diff --git a/src/pretix/base/invoice.py b/src/pretix/base/invoice.py
index b52979fe64..0fffe9d686 100644
--- a/src/pretix/base/invoice.py
+++ b/src/pretix/base/invoice.py
@@ -35,8 +35,8 @@ from django.utils.formats import date_format, localize
from django.utils.translation import (
get_language, gettext, gettext_lazy, pgettext,
)
-from reportlab.lib import pagesizes
-from reportlab.lib.enums import TA_LEFT, TA_RIGHT
+from reportlab.lib import colors, pagesizes
+from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_RIGHT
from reportlab.lib.styles import ParagraphStyle, StyleSheet1
from reportlab.lib.units import mm
from reportlab.pdfbase import pdfmetrics
@@ -44,8 +44,8 @@ from reportlab.pdfbase.pdfmetrics import stringWidth
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.pdfgen.canvas import Canvas
from reportlab.platypus import (
- BaseDocTemplate, Frame, KeepTogether, NextPageTemplate, PageTemplate,
- Paragraph, Spacer, Table, TableStyle,
+ BaseDocTemplate, Flowable, Frame, KeepTogether, NextPageTemplate,
+ PageTemplate, Paragraph, Spacer, Table, TableStyle,
)
from pretix.base.decimal import round_decimal
@@ -147,6 +147,8 @@ class BaseReportlabInvoiceRenderer(BaseInvoiceRenderer):
"""
stylesheet = StyleSheet1()
stylesheet.add(ParagraphStyle(name='Normal', fontName=self.font_regular, fontSize=10, leading=12))
+ stylesheet.add(ParagraphStyle(name='BoldInverseCenter', fontName=self.font_bold, fontSize=10, leading=12,
+ textColor=colors.white, alignment=TA_CENTER))
stylesheet.add(ParagraphStyle(name='InvoiceFrom', parent=stylesheet['Normal']))
stylesheet.add(ParagraphStyle(name='Heading1', fontName=self.font_bold, fontSize=15, leading=15 * 1.2))
stylesheet.add(ParagraphStyle(name='FineprintHeading', fontName=self.font_bold, fontSize=8, leading=12))
@@ -249,6 +251,31 @@ class BaseReportlabInvoiceRenderer(BaseInvoiceRenderer):
).strip().replace('
', '
').replace('\n', '
\n')
+class PaidMarker(Flowable):
+ def __init__(self, text='paid', color=None, font='OpenSansBd', size=20):
+ super().__init__()
+ self.text = text
+ self.color = color
+ self.font = font
+ self.size = size
+ self._showBoundary = True
+
+ def wrap(self, availwidth, availheight):
+ # Fake a size, we don't care if we exceed the table
+ return 10, self.size / 2
+
+ def draw(self):
+ self.canv.translate(0, - self.size / 2)
+ self.canv.rotate(2)
+ self.canv.setFont(self.font, self.size)
+ self.canv.setFillColor(self.color)
+ width = self.canv.stringWidth(self.text, self.font, self.size)
+ self.canv.drawRightString(0, 0, self.text)
+
+ self.canv.setStrokeColor(self.color)
+ self.canv.roundRect(-width - self.size / 2, -self.size / 4, width + self.size, self.size + self.size / 4, 3)
+
+
class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
identifier = 'classic'
verbose_name = pgettext('invoice', 'Classic renderer (pretix 1.0)')
@@ -612,10 +639,10 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
tdata.append([
pgettext('invoice', 'Invoice total'), '', money_filter(total, self.invoice.event.currency)
])
- colwidths = [a * doc.width for a in (.65, .05, .30)]
+ colwidths = [a * doc.width for a in (.65, .20, .15)]
- if self.invoice.event.settings.invoice_show_payments and not self.invoice.is_cancellation:
- if self.invoice.order.status == Order.STATUS_PENDING:
+ if not self.invoice.is_cancellation:
+ if self.invoice.event.settings.invoice_show_payments and self.invoice.order.status == Order.STATUS_PENDING:
pending_sum = self.invoice.order.pending_sum
if pending_sum != total:
tdata.append([pgettext('invoice', 'Received payments')] + (['', '', ''] if has_taxes else ['']) + [
@@ -627,7 +654,7 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
tstyledata += [
('FONTNAME', (0, len(tdata) - 3), (-1, len(tdata) - 3), self.font_bold),
]
- elif self.invoice.order.payments.filter(
+ elif self.invoice.event.settings.invoice_show_payments and self.invoice.order.payments.filter(
state__in=(OrderPayment.PAYMENT_STATE_CONFIRMED, OrderPayment.PAYMENT_STATE_REFUNDED), provider='giftcard'
).exists():
giftcard_sum = self.invoice.order.payments.filter(
@@ -645,6 +672,13 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
tstyledata += [
('FONTNAME', (0, len(tdata) - 3), (-1, len(tdata) - 3), self.font_bold),
]
+ elif self.invoice.payment_provider_stamp:
+ pm = PaidMarker(
+ text=self.invoice.payment_provider_stamp,
+ color=colors.HexColor(self.event.settings.theme_color_success),
+ size=16
+ )
+ tdata[-1][-2] = pm
table = Table(tdata, colWidths=colwidths, repeatRows=1)
table.setStyle(TableStyle(tstyledata))
diff --git a/src/pretix/base/migrations/0229_invoice_payment_provider_stamp.py b/src/pretix/base/migrations/0229_invoice_payment_provider_stamp.py
new file mode 100644
index 0000000000..da75d2d98e
--- /dev/null
+++ b/src/pretix/base/migrations/0229_invoice_payment_provider_stamp.py
@@ -0,0 +1,19 @@
+# Generated by Django 3.2.17 on 2023-02-07 10:00
+
+import django.core.serializers.json
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('pretixbase', '0228_scheduledeventexport_scheduledorganizerexport'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='invoice',
+ name='payment_provider_stamp',
+ field=models.CharField(max_length=100, null=True),
+ ),
+ ]
diff --git a/src/pretix/base/models/invoices.py b/src/pretix/base/models/invoices.py
index ece97d6f96..624fd5d934 100644
--- a/src/pretix/base/models/invoices.py
+++ b/src/pretix/base/models/invoices.py
@@ -95,6 +95,8 @@ class Invoice(models.Model):
:type additional_text: str
:param payment_provider_text: A payment provider specific text
:type payment_provider_text: str
+ :param payment_provider_stamp: A payment provider specific stamp
+ :type payment_provider_stamp: str
:param footer_text: A footer text, displayed smaller and centered on every page
:type footer_text: str
:param foreign_currency_display: A different currency that taxes should also be displayed in.
@@ -144,6 +146,7 @@ class Invoice(models.Model):
additional_text = models.TextField(blank=True)
reverse_charge = models.BooleanField(default=False)
payment_provider_text = models.TextField(blank=True)
+ payment_provider_stamp = models.CharField(max_length=100, null=True, blank=True)
footer_text = models.TextField(blank=True)
foreign_currency_display = models.CharField(max_length=50, null=True, blank=True)
diff --git a/src/pretix/base/payment.py b/src/pretix/base/payment.py
index 4901a7b07d..95b5ce9dd0 100644
--- a/src/pretix/base/payment.py
+++ b/src/pretix/base/payment.py
@@ -464,6 +464,16 @@ class BasePaymentProvider:
return pgettext_lazy('invoice', 'The payment for this invoice has already been received.')
return self.settings.get('_invoice_text', as_type=LazyI18nString, default='')
+ def render_invoice_stamp(self, order: Order, payment: OrderPayment) -> str:
+ """
+ This is called when an invoice for an order with this payment provider is generated.
+ The default implementation returns "paid" if the order was already paid, and ``None``
+ otherwise. You can override this with a string, but it should be *really* short to make
+ the invoice look pretty.
+ """
+ if order.status == Order.STATUS_PAID:
+ return _('paid')
+
@property
def payment_form_fields(self) -> dict:
"""
diff --git a/src/pretix/base/services/invoices.py b/src/pretix/base/services/invoices.py
index d5f68e4572..c5aa16dd0b 100644
--- a/src/pretix/base/services/invoices.py
+++ b/src/pretix/base/services/invoices.py
@@ -98,8 +98,10 @@ def build_invoice(invoice: Invoice) -> Invoice:
payment = str(lp.payment_provider.render_invoice_text(invoice.order, lp))
else:
payment = str(lp.payment_provider.render_invoice_text(invoice.order))
+ payment_stamp = lp.payment_provider.render_invoice_stamp(invoice.order, lp)
else:
payment = ""
+ payment_stamp = None
if invoice.event.settings.invoice_include_expire_date and invoice.order.status == Order.STATUS_PENDING:
if payment:
payment += "
"
@@ -111,6 +113,7 @@ def build_invoice(invoice: Invoice) -> Invoice:
invoice.additional_text = str(additional).replace('\n', '
')
invoice.footer_text = str(footer)
invoice.payment_provider_text = str(payment).replace('\n', '
')
+ invoice.payment_provider_stamp = str(payment_stamp) if payment_stamp else None
try:
ia = invoice.order.invoice_address
@@ -325,6 +328,7 @@ def generate_cancellation(invoice: Invoice, trigger_pdf=True):
cancellation.is_cancellation = True
cancellation.date = timezone.now().date()
cancellation.payment_provider_text = ''
+ cancellation.payment_provider_stamp = ''
cancellation.file = None
cancellation.sent_to_organizer = None
cancellation.sent_to_customer = None
@@ -436,6 +440,7 @@ def build_preview_invoice_pdf(event):
invoice.additional_text = str(additional).replace('\n', '
')
invoice.footer_text = str(footer)
invoice.payment_provider_text = str(payment).replace('\n', '
')
+ invoice.payment_provider_stamp = _('paid')
invoice.invoice_to_name = _("John Doe")
invoice.invoice_to_street = _("214th Example Street")
invoice.invoice_to_zipcode = _("012345")
diff --git a/src/tests/api/test_invoices.py b/src/tests/api/test_invoices.py
index cefe963161..a158e44a1e 100644
--- a/src/tests/api/test_invoices.py
+++ b/src/tests/api/test_invoices.py
@@ -176,6 +176,7 @@ TEST_INVOICE_RES = {
"internal_reference": "",
"additional_text": "",
"payment_provider_text": "",
+ "payment_provider_stamp": None,
"footer_text": "",
"foreign_currency_display": None,
"foreign_currency_rate": None,
diff --git a/src/tests/api/test_order_change.py b/src/tests/api/test_order_change.py
index da78eba7a7..6aaba9781e 100644
--- a/src/tests/api/test_order_change.py
+++ b/src/tests/api/test_order_change.py
@@ -445,6 +445,7 @@ def test_order_create_invoice(token_client, organizer, event, order):
'introductory_text': '',
'additional_text': '',
'payment_provider_text': '',
+ 'payment_provider_stamp': None,
'footer_text': '',
'lines': [
{