refactor payment qr codes

This commit is contained in:
Mira Weller
2025-12-02 13:15:41 +01:00
parent bdd94b1f8a
commit cc4ad998e1
4 changed files with 186 additions and 141 deletions
+129
View File
@@ -0,0 +1,129 @@
import text_unidecode
from django.utils.http import urlencode
from django.utils.safestring import mark_safe
from django.utils.translation import gettext_lazy as _
def dotdecimal(value):
return str(value).replace(",", ".")
def commadecimal(value):
return str(value).replace(".", ",")
def generate_payment_qr_codes(
event,
code,
amount,
bank_details_sepa_bic,
bank_details_sepa_name,
bank_details_sepa_iban
):
out = []
qr_data = swiss_qrbill(
event,
code,
amount,
bank_details_sepa_bic,
bank_details_sepa_name,
bank_details_sepa_iban
)
if qr_data:
out.append({
"id": "qrbill",
"label": "QR-bill",
"html_prefix": mark_safe('<svg class="banktransfer-swiss-cross" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 19.8 19.8"><path stroke="#fff" stroke-width="1.436" d="M.7.7h18.4v18.4H.7z"/><path fill="#fff" d="M8.3 4h3.3v11H8.3z"/><path fill="#fff" d="M4.4 7.9h11v3.3h-11z"/></svg>'),
"qr_data": qr_data,
"css_class": "banktransfer-swiss-cross-overlay",
})
if event.currency == 'EUR' and bank_details_sepa_iban:
out.append({
"id": "girocode",
"label": "EPC-QR",
"qr_data": "\n".join(text_unidecode.unidecode(str(d or '')) for d in [
"BCD", # Service Tag: BCD
"002", # Version: V2
"2", # Character set: ISO 8859-1
"SCT", # Identification code: SCT
bank_details_sepa_bic, # AT-23 BIC of the Beneficiary Bank
bank_details_sepa_name, # AT-21 Name of the Beneficiary
bank_details_sepa_iban, # AT-20 Account number of the Beneficiary
f"{event.currency}{dotdecimal(amount)}", # AT-04 Amount of the Credit Transfer in Euro
"", # AT-44 Purpose of the Credit Transfer
"", # AT-05 Remittance Information (Structured)
code, # AT-05 Remittance Information (Unstructured)
"", # Beneficiary to originator information
"",
]),
})
qr_data = f"bank://singlepaymentsepa?" + urlencode({
"name": bank_details_sepa_name,
"iban":bank_details_sepa_iban,
"bic": bank_details_sepa_bic,
"amount": commadecimal(amount),
"reason": code,
"currency": event.currency,
})
out.append({
"id": "bezahlcode",
"label": "BezahlCode",
"qr_data": mark_safe(qr_data),
"link": qr_data,
"link_aria_label": _("Open BezahlCode in your banking app to start the payment process."),
})
return out
def swiss_qrbill(
event,
code,
amount,
bank_details_sepa_bic,
bank_details_sepa_name,
bank_details_sepa_iban
):
if not bank_details_sepa_iban or not bank_details_sepa_iban[:2] in ('CH', 'LI'):
return
if event.currency not in ('EUR', 'CHF'):
return
if not event.settings.invoice_address_from or not event.settings.invoice_address_from_country:
return
data_fields = [
'SPC',
'0200',
'1',
bank_details_sepa_iban,
'K',
bank_details_sepa_name[:70],
event.settings.invoice_address_from.replace('\n', ', ')[:70],
(event.settings.invoice_address_from_zipcode + ' ' + event.settings.invoice_address_from_city)[:70],
'',
'',
str(event.settings.invoice_address_from_country),
'', # rfu
'', # rfu
'', # rfu
'', # rfu
'', # rfu
'', # rfu
'', # rfu
str(amount),
event.currency,
'', # debtor address
'', # debtor address
'', # debtor address
'', # debtor address
'', # debtor address
'', # debtor address
'', # debtor address
'NON',
'', # structured reference
code,
'EPD',
]
data_fields = [text_unidecode.unidecode(d or '') for d in data_fields]
return '\r\n'.join(data_fields)
+9 -49
View File
@@ -46,12 +46,12 @@ from i18nfield.forms import I18nTextInput
from i18nfield.strings import LazyI18nString
from localflavor.generic.forms import BICFormField, IBANFormField
from localflavor.generic.validators import IBANValidator
from text_unidecode import unidecode
from pretix.base.forms import I18nMarkdownTextarea
from pretix.base.models import InvoiceAddress, Order, OrderPayment, OrderRefund
from pretix.base.payment import BasePaymentProvider
from pretix.base.templatetags.money import money_filter
from pretix.helpers.payment import generate_payment_qr_codes
from pretix.plugins.banktransfer.templatetags.ibanformat import ibanformat
from pretix.presale.views.cart import cart_session
@@ -313,51 +313,6 @@ class BankTransfer(BasePaymentProvider):
t += str(self.settings.get('bank_details', as_type=LazyI18nString))
return t
def swiss_qrbill(self, payment):
if not self.settings.get('bank_details_sepa_iban') or not self.settings.get('bank_details_sepa_iban')[:2] in ('CH', 'LI'):
return
if self.event.currency not in ('EUR', 'CHF'):
return
if not self.event.settings.invoice_address_from or not self.event.settings.invoice_address_from_country:
return
data_fields = [
'SPC',
'0200',
'1',
self.settings.get('bank_details_sepa_iban'),
'K',
self.settings.get('bank_details_sepa_name')[:70],
self.event.settings.invoice_address_from.replace('\n', ', ')[:70],
(self.event.settings.invoice_address_from_zipcode + ' ' + self.event.settings.invoice_address_from_city)[:70],
'',
'',
str(self.event.settings.invoice_address_from_country),
'', # rfu
'', # rfu
'', # rfu
'', # rfu
'', # rfu
'', # rfu
'', # rfu
str(payment.amount),
self.event.currency,
'', # debtor address
'', # debtor address
'', # debtor address
'', # debtor address
'', # debtor address
'', # debtor address
'', # debtor address
'NON',
'', # structured reference
self._code(payment.order),
'EPD',
]
data_fields = [unidecode(d or '') for d in data_fields]
return '\r\n'.join(data_fields)
def payment_pending_render(self, request: HttpRequest, payment: OrderPayment):
template = get_template('pretixplugins/banktransfer/pending.html')
ctx = {
@@ -367,13 +322,18 @@ class BankTransfer(BasePaymentProvider):
'amount': payment.amount,
'payment_info': payment.info_data,
'settings': self.settings,
'swiss_qrbill': self.swiss_qrbill(payment),
'eu_barcodes': self.event.currency == 'EUR',
'payment_qr_codes': generate_payment_qr_codes(
event=self.event,
code=self._code(payment.order),
amount=payment.amount,
bank_details_sepa_bic=self.settings.get('bank_details_sepa_bic'),
bank_details_sepa_name=self.settings.get('bank_details_sepa_name'),
bank_details_sepa_iban=self.settings.get('bank_details_sepa_iban'),
) if self.settings.bank_details_type == "sepa" else None,
'pending_description': self.settings.get('pending_description', as_type=LazyI18nString),
'details': self.settings.get('bank_details', as_type=LazyI18nString),
'has_invoices': payment.order.invoices.exists(),
}
ctx['any_barcodes'] = ctx['swiss_qrbill'] or ctx['eu_barcodes']
return template.render(ctx, request=request)
def payment_control_render(self, request: HttpRequest, payment: OrderPayment) -> str:
@@ -1,7 +1,6 @@
{% load i18n %}
{% load l10n %}
{% load commadecimal %}
{% load static %}
{% load dotdecimal %}
{% load ibanformat %}
{% load money %}
@@ -17,7 +16,7 @@
{% endblocktrans %}</p>
<div class="row">
<div class="{% if settings.bank_details_type == "sepa" %}col-md-6{% else %}col-md-12{% endif %} col-xs-12">
<div class="{% if payment_qr_codes %}col-md-6{% else %}col-md-12{% endif %} col-xs-12">
<dl class="dl-horizontal">
<dt>{% trans "Reference code (important):" %}</dt><dd><b>{{ code }}</b></dd>
<dt>{% trans "Amount:" %}</dt><dd>{{ amount|money:event.currency }}</dd>
@@ -36,94 +35,7 @@
{% trans "We will send you an email as soon as we received your payment." %}
</p>
</div>
{% if settings.bank_details_type == "sepa" and any_barcodes %}
<div class="tabcontainer col-md-6 col-sm-6 hidden-xs text-center js-only blank-after">
<div id="banktransfer_qrcodes_tabs_content" class="tabpanels blank-after">
{% if swiss_qrbill %}
<div id="banktransfer_qrcodes_qrbill"
role="tabpanel"
tabindex="0"
aria-labelledby="banktransfer_qrcodes_qrbill_tab"
>
<div class="banktransfer-swiss-cross-overlay" role="figure" aria-labelledby="banktransfer_qrcodes_qrbill_tab banktransfer_qrcodes_label">
<svg class="banktransfer-swiss-cross" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 19.8 19.8"><path stroke="#fff" stroke-width="1.436" d="M.7.7h18.4v18.4H.7z"/><path fill="#fff" d="M8.3 4h3.3v11H8.3z"/><path fill="#fff" d="M4.4 7.9h11v3.3h-11z"/></svg>
<script type="text/plain" data-size="150" data-replace-with-qr data-desc="{% trans 'Scan this image with your banking apps QR-Reader to start the payment process.' %}">{{swiss_qrbill}}</script>
</div>
</div>
{% endif %}
{% if eu_barcodes %}
<div id="banktransfer_qrcodes_girocode"
role="tabpanel"
tabindex="0"
{{ swiss_qrbill|yesno:'hidden,' }}
aria-labelledby="banktransfer_qrcodes_girocode_tab"
>
<div role="figure" aria-labelledby="banktransfer_qrcodes_girocode_tab banktransfer_qrcodes_label">
<script type="text/plain" data-size="150" data-replace-with-qr data-desc="{% trans 'Scan this image with your banking apps QR-Reader to start the payment process.' %}">BCD
002
2
SCT
{{ settings.bank_details_sepa_bic }}
{{ settings.bank_details_sepa_name|unidecode }}
{{ settings.bank_details_sepa_iban }}
{{ event.currency }}{{ amount|dotdecimal }}
{{ code }}
</script>
</div>
</div>
<div id="banktransfer_qrcodes_bezahlcode"
role="tabpanel"
tabindex="0"
hidden
aria-labelledby="banktransfer_qrcodes_bezahlcode_tab"
>
<a aria-label="{% trans "Open BezahlCode in your banking app to start the payment process." %}" href="bank://singlepaymentsepa?name={{ settings.bank_details_sepa_name|urlencode }}&iban={{ settings.bank_details_sepa_iban }}&bic={{ settings.bank_details_sepa_bic }}&amount={{ amount|commadecimal }}&reason={{ code }}&currency={{ event.currency }}">
<div role="figure" aria-labelledby="banktransfer_qrcodes_bezahlcode_tab banktransfer_qrcodes_label">
<script type="text/plain" data-size="150" data-replace-with-qr data-desc="{% trans 'Scan this image with your banking apps QR-Reader to start the payment process.' %}">bank://singlepaymentsepa?name={{ settings.bank_details_sepa_name|urlencode }}&iban={{ settings.bank_details_sepa_iban }}&bic={{ settings.bank_details_sepa_bic }}&amount={{ amount|commadecimal }}&reason={{ code }}&currency={{ event.currency }}</script>
</div>
</a>
</div>
{% endif %}
</div>
<div id="banktransfer_qrcodes_tabs" role="tablist" aria-labelledby="banktransfer_qrcodes_label" class="blank-after btn-group">
{% if swiss_qrbill %}
<button
class="btn btn-default"
id="banktransfer_qrcodes_qrbill_tab"
type="button"
role="tab"
aria-controls="banktransfer_qrcodes_qrbill"
aria-selected="true"
tabindex="-1">QR-bill</button>
{% endif %}
{% if eu_barcodes %}
<button
class="btn btn-default"
id="banktransfer_qrcodes_girocode_tab"
type="button"
role="tab"
aria-controls="banktransfer_qrcodes_girocode"
aria-selected="{{ swiss_qrbill|yesno:"false,true" }}"
tabindex="-1">EPC-QR</button>
<button
class="btn btn-default"
id="banktransfer_qrcodes_bezahlcode_tab"
type="button"
role="tab"
aria-controls="banktransfer_qrcodes_bezahlcode"
aria-selected="false"
tabindex="-1">BezahlCode</button>
{% endif %}
</div>
<p class="text-muted" id="banktransfer_qrcodes_label">
{% trans "Scan the QR code with your banking app" %}
</p>
</div>
{% if payment_qr_codes %}
{% include "pretixpresale/event/payment_qr_codes.html" %}
{% endif %}
</div>
{% if swiss_qrbill %}
<link rel="stylesheet" href="{% static "pretixplugins/banktransfer/swisscross.css" %}">
{% endif %}
</div>
@@ -0,0 +1,44 @@
{% load i18n %}
{% load static %}
{% if payment_qr_codes %}
<div class="tabcontainer col-md-6 col-sm-6 hidden-xs text-center js-only blank-after">
<div id="banktransfer_qrcodes_tabs_content" class="tabpanels blank-after">
{% for code_info in payment_qr_codes %}
<div id="banktransfer_qrcodes_{{ code_info.id }}"
role="tabpanel"
tabindex="0"
{% if not forloop.first %}hidden{% endif %}
aria-labelledby="banktransfer_qrcodes_{{ code_info.id }}_tab"
>
{% if code_info.link %}<a aria-label="{{ code_info.link_aria_label }}" href="{{ code_info.link }}">{% endif %}
<div class="{{ code_info.css_class }}" role="figure" aria-labelledby="banktransfer_qrcodes_{{ code_info.id }}_tab banktransfer_qrcodes_label">
{{ code_info.html_prefix }}
<script type="text/plain" data-size="150" data-replace-with-qr data-desc="{% trans 'Scan this image with your banking apps QR-Reader to start the payment process.' %}">{{ code_info.qr_data }}</script>
</div>
{% if code_info.link %}</a>{% endif %}
</div>
{% endfor %}
</div>
<div id="banktransfer_qrcodes_tabs" role="tablist" aria-labelledby="banktransfer_qrcodes_label" class="blank-after btn-group">
{% for code_info in payment_qr_codes %}
<button
class="btn btn-default"
id="banktransfer_qrcodes_{{ code_info.id }}_tab"
type="button"
role="tab"
aria-controls="banktransfer_qrcodes_{{ code_info.id }}"
aria-selected="{{ forloop.first|yesno:"true,false" }}"
tabindex="-1">{{ code_info.label }}</button>
{% endfor %}
</div>
<p class="text-muted" id="banktransfer_qrcodes_label">
{% trans "Scan the QR code with your banking app" %}
</p>
</div>
{% for code_info in payment_qr_codes %}
{% if code_info.id == "qrbill" %}
<link rel="stylesheet" href="{% static "pretixplugins/banktransfer/swisscross.css" %}">
{% endif %}
{% endfor %}
{% endif %}