diff --git a/doc/development/api/payment.rst b/doc/development/api/payment.rst index e1edbf2848..a58c533b78 100644 --- a/doc/development/api/payment.rst +++ b/doc/development/api/payment.rst @@ -110,6 +110,8 @@ The provider class .. automethod:: payment_partial_refund_supported + .. automethod:: payment_presale_render + .. automethod:: execute_refund .. automethod:: refund_control_render diff --git a/src/pretix/base/payment.py b/src/pretix/base/payment.py index 0cf01e8f73..bb7907ff1c 100644 --- a/src/pretix/base/payment.py +++ b/src/pretix/base/payment.py @@ -706,7 +706,7 @@ class BasePaymentProvider: It should return HTML code containing information regarding the current payment status and, if applicable, next steps. - The default implementation returns the verbose name of the payment provider. + The default implementation returns an empty string. :param order: The order object """ @@ -725,6 +725,19 @@ class BasePaymentProvider: """ return '' + def payment_presale_render(self, payment: OrderPayment) -> str: + """ + Will be called if the *ticket customer* views the details of a payment. This is + currently used e.g. when the customer requests a refund to show which payment + method is used for the refund. This should only include very basic information + about the payment, such das "VISA card ****9999", and never raw payment information. + + The default implementation returns the public name of the payment provider. + + :param order: The order object + """ + return self.public_name + def payment_refund_supported(self, payment: OrderPayment) -> bool: """ Will be called to check if the provider supports automatic refunding for this diff --git a/src/pretix/plugins/banktransfer/payment.py b/src/pretix/plugins/banktransfer/payment.py index 6590ab0f68..fdb033c4c5 100644 --- a/src/pretix/plugins/banktransfer/payment.py +++ b/src/pretix/plugins/banktransfer/payment.py @@ -6,7 +6,7 @@ from django import forms from django.core.exceptions import ValidationError from django.http import HttpRequest from django.template.loader import get_template -from django.utils.translation import gettext_lazy as _ +from django.utils.translation import gettext, gettext_lazy as _ from i18nfield.fields import I18nFormField, I18nTextarea from i18nfield.forms import I18nTextInput from i18nfield.strings import LazyI18nString @@ -282,6 +282,18 @@ class BankTransfer(BasePaymentProvider): def payment_partial_refund_supported(self, payment: OrderPayment) -> bool: return self.payment_refund_supported(payment) + def payment_presale_render(self, payment: OrderPayment) -> str: + pi = payment.info_data or {} + if self.payment_refund_supported(payment): + try: + iban = self.norm(pi['iban']) + return gettext('Bank account {iban}').format( + iban=iban[0:2] + '****' + iban[-4:] + ) + except: + pass + return super().payment_presale_render(payment) + def execute_refund(self, refund: OrderRefund): """ We just keep a created refund object. It will be marked as done using the control view diff --git a/src/pretix/plugins/stripe/payment.py b/src/pretix/plugins/stripe/payment.py index c29db066af..f05e207288 100644 --- a/src/pretix/plugins/stripe/payment.py +++ b/src/pretix/plugins/stripe/payment.py @@ -864,6 +864,21 @@ class StripeCC(StripeMethod): raise PaymentException(_('We had trouble communicating with Stripe. Please try again and get in touch ' 'with us if this problem persists.')) + def payment_presale_render(self, payment: OrderPayment) -> str: + pi = payment.info_data or {} + try: + if "charges" in pi: + card = pi["charges"]["data"][0]["payment_method_details"]["card"] + else: + card = pi["source"]["card"] + except: + logger.exception('Could not parse payment data') + return super().payment_presale_render(payment) + return f'{self.public_name}: ' \ + f'{card.get("brand", "").title()} ' \ + f'************{card.get("last4", "****")}, ' \ + f'{_("expires {month}/{year}").format(month=card.get("exp_month"), year=card.get("exp_year"))}' + class StripeGiropay(StripeMethod): identifier = 'stripe_giropay' @@ -928,6 +943,14 @@ class StripeGiropay(StripeMethod): return True return False + def payment_presale_render(self, payment: OrderPayment) -> str: + pi = payment.info_data or {} + try: + return gettext('Bank account at {bank}').format(bank=pi["source"]["giropay"]["bank_name"]) + except: + logger.exception('Could not parse payment data') + return super().payment_presale_render(payment) + class StripeIdeal(StripeMethod): identifier = 'stripe_ideal' @@ -972,6 +995,14 @@ class StripeIdeal(StripeMethod): def checkout_prepare(self, request, cart): return True + def payment_presale_render(self, payment: OrderPayment) -> str: + pi = payment.info_data or {} + try: + return gettext('Bank account at {bank}').format(bank=pi["source"]["ideal"]["bank"]) + except: + logger.exception('Could not parse payment data') + return super().payment_presale_render(payment) + class StripeAlipay(StripeMethod): identifier = 'stripe_alipay' @@ -1079,6 +1110,14 @@ class StripeBancontact(StripeMethod): return True return False + def payment_presale_render(self, payment: OrderPayment) -> str: + pi = payment.info_data or {} + try: + return gettext('Bank account at {bank}').format(bank=pi["source"]["bancontact"]["bank_name"]) + except: + logger.exception('Could not parse payment data') + return super().payment_presale_render(payment) + class StripeSofort(StripeMethod): identifier = 'stripe_sofort' @@ -1148,6 +1187,17 @@ class StripeSofort(StripeMethod): def payment_can_retry(self, payment): return payment.state != OrderPayment.PAYMENT_STATE_PENDING and self._is_still_available(order=payment.order) + def payment_presale_render(self, payment: OrderPayment) -> str: + pi = payment.info_data or {} + try: + return gettext('Bank account {iban} at {bank}').format( + iban=f'{pi["source"]["sofort"]["country"]}****{pi["source"]["sofort"]["iban_last4"]}', + bank=pi["source"]["sofort"]["bank_name"] + ) + except: + logger.exception('Could not parse payment data') + return super().payment_presale_render(payment) + class StripeEPS(StripeMethod): identifier = 'stripe_eps' @@ -1212,6 +1262,14 @@ class StripeEPS(StripeMethod): return True return False + def payment_presale_render(self, payment: OrderPayment) -> str: + pi = payment.info_data or {} + try: + return gettext('Bank account at {bank}').format(bank=pi["source"]["eps"]["bank"].replace('_', '').title()) + except: + logger.exception('Could not parse payment data') + return super().payment_presale_render(payment) + class StripeMultibanco(StripeMethod): identifier = 'stripe_multibanco' @@ -1307,6 +1365,14 @@ class StripePrzelewy24(StripeMethod): def checkout_prepare(self, request, cart): return True + def payment_presale_render(self, payment: OrderPayment) -> str: + pi = payment.info_data or {} + try: + return gettext('Bank account at {bank}').format(bank=pi["source"]["p24"]["bank"].replace('_', '').title()) + except: + logger.exception('Could not parse payment data') + return super().payment_presale_render(payment) + class StripeWeChatPay(StripeMethod): identifier = 'stripe_wechatpay'