diff --git a/pyproject.toml b/pyproject.toml
index c2e185d71..689e5adca 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -93,7 +93,7 @@ dependencies = [
"requests==2.31.*",
"sentry-sdk==2.27.*",
"sepaxml==2.6.*",
- "stripe==7.9.*",
+ "stripe==12.1.*",
"text-unidecode==1.*",
"tlds>=2020041600",
"tqdm==4.*",
diff --git a/src/pretix/plugins/stripe/management/commands/stripe_connect_fill_countries.py b/src/pretix/plugins/stripe/management/commands/stripe_connect_fill_countries.py
index 7062e9b1f..a7099ef98 100644
--- a/src/pretix/plugins/stripe/management/commands/stripe_connect_fill_countries.py
+++ b/src/pretix/plugins/stripe/management/commands/stripe_connect_fill_countries.py
@@ -19,12 +19,12 @@
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# .
#
-import stripe
from django.core.management.base import BaseCommand
from django_scopes import scopes_disabled
from pretix.base.models import Event
from pretix.base.settings import GlobalSettingsObject
+from pretix.plugins.stripe.utils import get_stripe_client
class Command(BaseCommand):
@@ -34,7 +34,10 @@ class Command(BaseCommand):
def handle(self, *args, **options):
cache = {}
gs = GlobalSettingsObject()
- api_key = gs.settings.payment_stripe_connect_secret_key or gs.settings.payment_stripe_connect_test_secret_key
+ api_key = (
+ gs.settings.payment_stripe_connect_secret_key
+ or gs.settings.payment_stripe_connect_test_secret_key
+ )
if not api_key:
self.stderr.write(self.style.ERROR("Stripe Connect is not set up!"))
return
@@ -46,11 +49,13 @@ class Command(BaseCommand):
e.settings.payment_stripe_merchant_country = cache[uid]
else:
try:
- account = stripe.Account.retrieve(
+ stripe_client = get_stripe_client(api_key)
+ account = stripe_client.accounts.retrieve(
uid,
- api_key=api_key
)
except Exception as e:
print(e)
else:
- e.settings.payment_stripe_merchant_country = cache[uid] = account.get('country')
+ e.settings.payment_stripe_merchant_country = cache[uid] = (
+ account.get("country")
+ )
diff --git a/src/pretix/plugins/stripe/payment.py b/src/pretix/plugins/stripe/payment.py
index 2bf8ad4a2..979cac189 100644
--- a/src/pretix/plugins/stripe/payment.py
+++ b/src/pretix/plugins/stripe/payment.py
@@ -58,17 +58,24 @@ from django.utils.translation import gettext, gettext_lazy as _, pgettext
from django_countries import countries
from text_unidecode import unidecode
-from pretix import __version__
from pretix.base.decimal import round_decimal
from pretix.base.forms import SecretKeySettingsField
from pretix.base.forms.questions import (
- guess_country, guess_country_from_request,
+ guess_country,
+ guess_country_from_request,
)
from pretix.base.models import (
- Event, InvoiceAddress, Order, OrderPayment, OrderRefund, Quota,
+ Event,
+ InvoiceAddress,
+ Order,
+ OrderPayment,
+ OrderRefund,
+ Quota,
)
from pretix.base.payment import (
- BasePaymentProvider, PaymentException, WalletQueries,
+ BasePaymentProvider,
+ PaymentException,
+ WalletQueries,
)
from pretix.base.plugins import get_all_plugins
from pretix.base.services.mail import SendMailException
@@ -80,14 +87,17 @@ from pretix.helpers.urls import build_absolute_uri as build_global_uri
from pretix.multidomain.urlreverse import build_absolute_uri
from pretix.plugins.stripe.forms import StripeKeyValidator
from pretix.plugins.stripe.models import (
- ReferencedStripeObject, RegisteredApplePayDomain,
+ ReferencedStripeObject,
+ RegisteredApplePayDomain,
)
from pretix.plugins.stripe.tasks import (
- get_stripe_account_key, stripe_verify_domain,
+ get_stripe_account_key,
+ stripe_verify_domain,
)
+from pretix.plugins.stripe.utils import get_stripe_client
from pretix.presale.views.cart import cart_session
-logger = logging.getLogger('pretix.plugins.stripe')
+logger = logging.getLogger("pretix.plugins.stripe")
# State of the payment methods
@@ -159,26 +169,26 @@ logger = logging.getLogger('pretix.plugins.stripe')
class StripeSettingsHolder(BasePaymentProvider):
- identifier = 'stripe_settings'
- verbose_name = _('Stripe')
+ identifier = "stripe_settings"
+ verbose_name = _("Stripe")
is_enabled = False
is_meta = True
def __init__(self, event: Event):
super().__init__(event)
- self.settings = SettingsSandbox('payment', 'stripe', event)
+ self.settings = SettingsSandbox("payment", "stripe", event)
def get_connect_url(self, request):
- request.session['payment_stripe_oauth_event'] = request.event.pk
- if 'payment_stripe_oauth_token' not in request.session:
- request.session['payment_stripe_oauth_token'] = get_random_string(32)
+ request.session["payment_stripe_oauth_event"] = request.event.pk
+ if "payment_stripe_oauth_token" not in request.session:
+ request.session["payment_stripe_oauth_token"] = get_random_string(32)
return (
"https://connect.stripe.com/oauth/authorize?response_type=code&client_id={}&state={}"
"&scope=read_write&redirect_uri={}"
).format(
self.settings.connect_client_id,
- request.session['payment_stripe_oauth_token'],
- urllib.parse.quote(build_global_uri('plugins:stripe:oauth.return')),
+ request.session["payment_stripe_oauth_token"],
+ urllib.parse.quote(build_global_uri("plugins:stripe:oauth.return")),
)
def settings_content_render(self, request):
@@ -186,55 +196,64 @@ class StripeSettingsHolder(BasePaymentProvider):
# Use Stripe connect
if not self.settings.connect_user_id:
return (
- "
{}
"
- "{}"
+ "{}
" "{}"
).format(
- _('To accept payments via Stripe, you will need an account at Stripe. By clicking on the '
- 'following button, you can either create a new Stripe account connect pretix to an existing '
- 'one.'),
+ _(
+ "To accept payments via Stripe, you will need an account at Stripe. By clicking on the "
+ "following button, you can either create a new Stripe account connect pretix to an existing "
+ "one."
+ ),
self.get_connect_url(request),
- _('Connect with Stripe')
+ _("Connect with Stripe"),
)
else:
- return (
- "{}"
- ).format(
- reverse('plugins:stripe:oauth.disconnect', kwargs={
- 'organizer': self.event.organizer.slug,
- 'event': self.event.slug,
- }),
- _('Disconnect from Stripe')
+ return ("{}").format(
+ reverse(
+ "plugins:stripe:oauth.disconnect",
+ kwargs={
+ "organizer": self.event.organizer.slug,
+ "event": self.event.slug,
+ },
+ ),
+ _("Disconnect from Stripe"),
)
else:
return "%s
%s
" % (
- _('Please configure a Stripe Webhook to '
- 'the following endpoint in order to automatically cancel orders when charges are refunded externally '
- 'and to process asynchronous payment methods like SOFORT.'),
- build_global_uri('plugins:stripe:webhook')
+ _(
+ 'Please configure a Stripe Webhook to '
+ "the following endpoint in order to automatically cancel orders when charges are refunded externally "
+ "and to process asynchronous payment methods like SOFORT."
+ ),
+ build_global_uri("plugins:stripe:webhook"),
)
@property
def settings_form_fields(self):
- if 'pretix_resellers' in [p.module for p in get_all_plugins()]:
+ if "pretix_resellers" in [p.module for p in get_all_plugins()]:
moto_settings = [
- ('reseller_moto',
- forms.BooleanField(
- label=_('Enable MOTO payments for resellers'),
- help_text=(
- _('Gated feature (needs to be enabled for your account by Stripe support first)') +
- '%s
' % _(
- 'We can flag the credit card transaction you make through the reseller interface as MOTO '
- '(Mail Order / Telephone Order), which will exempt them from Strong Customer '
- 'Authentication (SCA) requirements. However: By enabling this feature, you will need to '
- 'fill out yearly PCI-DSS self-assessment forms like the 40 page SAQ D. Please consult the '
- '%s for further information on this subject.' %
- '{}'.format(
- _('Stripe Integration security guide')
- )
- )
- ),
- required=False,
- ))
+ (
+ "reseller_moto",
+ forms.BooleanField(
+ label=_("Enable MOTO payments for resellers"),
+ help_text=(
+ _(
+ "Gated feature (needs to be enabled for your account by Stripe support first)"
+ )
+ + '%s
'
+ % _(
+ "We can flag the credit card transaction you make through the reseller interface as MOTO "
+ "(Mail Order / Telephone Order), which will exempt them from Strong Customer "
+ "Authentication (SCA) requirements. However: By enabling this feature, you will need to "
+ "fill out yearly PCI-DSS self-assessment forms like the 40 page SAQ D. Please consult the "
+ "%s for further information on this subject."
+ % '{}'.format(
+ _("Stripe Integration security guide")
+ )
+ )
+ ),
+ required=False,
+ ),
+ )
]
else:
moto_settings = []
@@ -243,234 +262,355 @@ class StripeSettingsHolder(BasePaymentProvider):
# Stripe connect
if self.settings.connect_user_id:
fields = [
- ('connect_user_name',
- forms.CharField(
- label=_('Stripe account'),
- disabled=True
- )),
- ('connect_user_id',
- forms.CharField(
- label=_('Stripe account'),
- disabled=True
- )),
- ('endpoint',
- forms.ChoiceField(
- label=_('Endpoint'),
- initial='live',
- choices=(
- ('live', pgettext('stripe', 'Live')),
- ('test', pgettext('stripe', 'Testing')),
- ),
- help_text=_('If your event is in test mode, we will always use Stripe\'s test API, '
- 'regardless of this setting.')
- )),
+ (
+ "connect_user_name",
+ forms.CharField(label=_("Stripe account"), disabled=True),
+ ),
+ (
+ "connect_user_id",
+ forms.CharField(label=_("Stripe account"), disabled=True),
+ ),
+ (
+ "endpoint",
+ forms.ChoiceField(
+ label=_("Endpoint"),
+ initial="live",
+ choices=(
+ ("live", pgettext("stripe", "Live")),
+ ("test", pgettext("stripe", "Testing")),
+ ),
+ help_text=_(
+ "If your event is in test mode, we will always use Stripe's test API, "
+ "regardless of this setting."
+ ),
+ ),
+ ),
]
else:
return {}
else:
allcountries = list(countries)
- allcountries.insert(0, ('', _('Select country')))
+ allcountries.insert(0, ("", _("Select country")))
fields = [
- ('publishable_key',
- forms.CharField(
- label=_('Publishable key'),
- help_text='{text}
'
- '{help}
'.format(
- text=_('Generate API keys'),
- docs_url='https://marketplace.stripe.com/apps/install/link/eu.pretix.plugins.stripe.rak',
- help=_('The button above will install our Stripe app to your account and will generate you '
- 'API keys with the recommended permission level for optimal usage with pretix.')
- ),
- validators=(
- StripeKeyValidator('pk_'),
- ),
- )),
- ('secret_key',
- SecretKeySettingsField(
- label=_('Secret key'),
- validators=(
- StripeKeyValidator(['sk_', 'rk_']),
- ),
- )),
- ('merchant_country',
- forms.ChoiceField(
- choices=allcountries,
- label=_('Merchant country'),
- help_text=_('The country in which your Stripe-account is registered in. Usually, this is your '
- 'country of residence.'),
- )),
+ (
+ "publishable_key",
+ forms.CharField(
+ label=_("Publishable key"),
+ help_text='{text}
'
+ '{help}
'.format(
+ text=_("Generate API keys"),
+ docs_url="https://marketplace.stripe.com/apps/install/link/eu.pretix.plugins.stripe.rak",
+ help=_(
+ "The button above will install our Stripe app to your account and will generate you "
+ "API keys with the recommended permission level for optimal usage with pretix."
+ ),
+ ),
+ validators=(StripeKeyValidator("pk_"),),
+ ),
+ ),
+ (
+ "secret_key",
+ SecretKeySettingsField(
+ label=_("Secret key"),
+ validators=(StripeKeyValidator(["sk_", "rk_"]),),
+ ),
+ ),
+ (
+ "merchant_country",
+ forms.ChoiceField(
+ choices=allcountries,
+ label=_("Merchant country"),
+ help_text=_(
+ "The country in which your Stripe-account is registered in. Usually, this is your "
+ "country of residence."
+ ),
+ ),
+ ),
]
extra_fields = [
- ('walletdetection',
- forms.BooleanField(
- label=mark_safe(
- _('Check for Apple Pay/Google Pay') +
- ' ' +
- '{}'.format(_('experimental'))
- ),
- help_text=_("pretix will attempt to check if the customer's web browser supports wallet-based payment "
- "methods like Apple Pay or Google Pay and display them prominently with the credit card "
- "payment method. This detection does not take into consideration if Google Pay/Apple Pay "
- "has been disabled in the Stripe Dashboard."),
- initial=True,
- required=False,
- )),
- ('postfix',
- forms.CharField(
- label=_('Statement descriptor postfix'),
- help_text=_('Any value entered here will be shown on the customer\'s credit card bill or bank account '
- 'transaction. We will automatically add the order code in front of it. Note that depending '
- 'on the payment method, only a very limited number of characters is allowed. We do not '
- 'recommend entering more than {cnt} characters into this field.').format(
- cnt=22 - 1 - settings.ENTROPY['order_code']
- ),
- required=False,
- )),
+ (
+ "walletdetection",
+ forms.BooleanField(
+ label=mark_safe(
+ _("Check for Apple Pay/Google Pay")
+ + " "
+ + '{}'.format(
+ _("experimental")
+ )
+ ),
+ help_text=_(
+ "pretix will attempt to check if the customer's web browser supports wallet-based payment "
+ "methods like Apple Pay or Google Pay and display them prominently with the credit card "
+ "payment method. This detection does not take into consideration if Google Pay/Apple Pay "
+ "has been disabled in the Stripe Dashboard."
+ ),
+ initial=True,
+ required=False,
+ ),
+ ),
+ (
+ "postfix",
+ forms.CharField(
+ label=_("Statement descriptor postfix"),
+ help_text=_(
+ "Any value entered here will be shown on the customer's credit card bill or bank account "
+ "transaction. We will automatically add the order code in front of it. Note that depending "
+ "on the payment method, only a very limited number of characters is allowed. We do not "
+ "recommend entering more than {cnt} characters into this field."
+ ).format(cnt=22 - 1 - settings.ENTROPY["order_code"]),
+ required=False,
+ ),
+ ),
]
d = OrderedDict(
- fields + [
- ('method_card',
- forms.BooleanField(
- label=_('Credit card payments'),
- required=False,
- )),
- ('method_ideal',
- forms.BooleanField(
- label=_('iDEAL'),
- disabled=self.event.currency != 'EUR',
- help_text=_('Some payment methods might need to be enabled in the settings of your Stripe account '
- 'before they work properly.'),
- required=False,
- )),
- ('method_alipay',
- forms.BooleanField(
- label=_('Alipay'),
- disabled=self.event.currency not in ('EUR', 'AUD', 'CAD', 'GBP', 'HKD', 'JPY', 'NZD', 'SGD', 'USD'),
- help_text=_('Some payment methods might need to be enabled in the settings of your Stripe account '
- 'before they work properly.'),
- required=False,
- )),
- ('method_bancontact',
- forms.BooleanField(
- label=_('Bancontact'),
- disabled=self.event.currency != 'EUR',
- help_text=_('Some payment methods might need to be enabled in the settings of your Stripe account '
- 'before they work properly.'),
- required=False,
- )),
- ('method_sepa_debit',
- forms.BooleanField(
- label=_('SEPA Direct Debit'),
- disabled=self.event.currency != 'EUR',
- help_text=(
- _('Some payment methods might need to be enabled in the settings of your Stripe account '
- 'before work properly.') +
- '%s
' % _(
- 'SEPA Direct Debit payments via Stripe are not processed '
- 'instantly but might take up to 14 days to be confirmed in some cases. '
- 'Please only activate this payment method if your payment term allows for this lag.'
- )),
- required=False,
- )),
- ('sepa_creditor_name',
- forms.CharField(
- label=_('SEPA Creditor Mandate Name'),
- disabled=self.event.currency != 'EUR',
- help_text=_('Please provide your SEPA Creditor Mandate Name, that will be displayed to the user.'),
- required=False,
- widget=forms.TextInput(
- attrs={
- 'data-display-dependency': '#id_payment_stripe_method_sepa_debit',
- 'data-required-if': '#id_payment_stripe_method_sepa_debit'
- }
- ),
- )),
- ('method_eps',
- forms.BooleanField(
- label=_('EPS'),
- disabled=self.event.currency != 'EUR',
- help_text=_('Some payment methods might need to be enabled in the settings of your Stripe account '
- 'before they work properly.'),
- required=False,
- )),
- ('method_multibanco',
- forms.BooleanField(
- label=_('Multibanco'),
- disabled=self.event.currency != 'EUR',
- help_text=_('Some payment methods might need to be enabled in the settings of your Stripe account '
- 'before they work properly.'),
- required=False,
- )),
- ('method_przelewy24',
- forms.BooleanField(
- label=_('Przelewy24'),
- disabled=self.event.currency not in ['EUR', 'PLN'],
- help_text=_('Some payment methods might need to be enabled in the settings of your Stripe account '
- 'before they work properly.'),
- required=False,
- )),
- ('method_wechatpay',
- forms.BooleanField(
- label=_('WeChat Pay'),
- disabled=self.event.currency not in ['AUD', 'CAD', 'EUR', 'GBP', 'HKD', 'JPY', 'SGD', 'USD'],
- help_text=_('Some payment methods might need to be enabled in the settings of your Stripe account '
- 'before they work properly.'),
- required=False,
- )),
- ('method_revolut_pay',
- forms.BooleanField(
- label='Revolut Pay',
- disabled=self.event.currency not in ['EUR', 'GBP'],
- help_text=_('Some payment methods might need to be enabled in the settings of your Stripe account '
- 'before they work properly.'),
- required=False,
- )),
- ('method_swish',
- forms.BooleanField(
- label=_('Swish'),
- disabled=self.event.currency != 'SEK',
- help_text=_('Some payment methods might need to be enabled in the settings of your Stripe account '
- 'before they work properly.'),
- required=False,
- )),
- ('method_twint',
- forms.BooleanField(
- label='TWINT',
- disabled=self.event.currency != 'CHF',
- help_text=_('Some payment methods might need to be enabled in the settings of your Stripe account '
- 'before they work properly.'),
- required=False,
- )),
- ('method_affirm',
- forms.BooleanField(
- label=_('Affirm'),
- disabled=self.event.currency not in ['USD', 'CAD'],
- help_text=' '.join([
- str(_('Some payment methods might need to be enabled in the settings of your Stripe account '
- 'before they work properly.')),
- str(_('Only available for payments between $50 and $30,000.'))
- ]),
- required=False,
- )),
- ('method_klarna',
- forms.BooleanField(
- label=_('Klarna'),
- disabled=self.event.currency not in [
- 'AUD', 'CAD', 'CHF', 'CZK', 'DKK', 'EUR', 'GBP', 'NOK', 'NZD', 'PLN', 'SEK', 'USD'
- ],
- help_text=' '.join([
- str(_('Some payment methods might need to be enabled in the settings of your Stripe account '
- 'before they work properly.')),
- str(_('Klarna and Stripe will decide which of the payment methods offered by Klarna are '
- 'available to the user.')),
- str(_('Klarna\'s terms of services do not allow it to be used by charities or political '
- 'organizations.')),
- ]),
- required=False,
- )),
+ fields
+ + [
+ (
+ "method_card",
+ forms.BooleanField(
+ label=_("Credit card payments"),
+ required=False,
+ ),
+ ),
+ (
+ "method_ideal",
+ forms.BooleanField(
+ label=_("iDEAL"),
+ disabled=self.event.currency != "EUR",
+ help_text=_(
+ "Some payment methods might need to be enabled in the settings of your Stripe account "
+ "before they work properly."
+ ),
+ required=False,
+ ),
+ ),
+ (
+ "method_alipay",
+ forms.BooleanField(
+ label=_("Alipay"),
+ disabled=self.event.currency
+ not in (
+ "EUR",
+ "AUD",
+ "CAD",
+ "GBP",
+ "HKD",
+ "JPY",
+ "NZD",
+ "SGD",
+ "USD",
+ ),
+ help_text=_(
+ "Some payment methods might need to be enabled in the settings of your Stripe account "
+ "before they work properly."
+ ),
+ required=False,
+ ),
+ ),
+ (
+ "method_bancontact",
+ forms.BooleanField(
+ label=_("Bancontact"),
+ disabled=self.event.currency != "EUR",
+ help_text=_(
+ "Some payment methods might need to be enabled in the settings of your Stripe account "
+ "before they work properly."
+ ),
+ required=False,
+ ),
+ ),
+ (
+ "method_sepa_debit",
+ forms.BooleanField(
+ label=_("SEPA Direct Debit"),
+ disabled=self.event.currency != "EUR",
+ help_text=(
+ _(
+ "Some payment methods might need to be enabled in the settings of your Stripe account "
+ "before work properly."
+ )
+ + '%s
'
+ % _(
+ "SEPA Direct Debit payments via Stripe are not processed "
+ "instantly but might take up to 14 days to be confirmed in some cases. "
+ "Please only activate this payment method if your payment term allows for this lag."
+ )
+ ),
+ required=False,
+ ),
+ ),
+ (
+ "sepa_creditor_name",
+ forms.CharField(
+ label=_("SEPA Creditor Mandate Name"),
+ disabled=self.event.currency != "EUR",
+ help_text=_(
+ "Please provide your SEPA Creditor Mandate Name, that will be displayed to the user."
+ ),
+ required=False,
+ widget=forms.TextInput(
+ attrs={
+ "data-display-dependency": "#id_payment_stripe_method_sepa_debit",
+ "data-required-if": "#id_payment_stripe_method_sepa_debit",
+ }
+ ),
+ ),
+ ),
+ (
+ "method_eps",
+ forms.BooleanField(
+ label=_("EPS"),
+ disabled=self.event.currency != "EUR",
+ help_text=_(
+ "Some payment methods might need to be enabled in the settings of your Stripe account "
+ "before they work properly."
+ ),
+ required=False,
+ ),
+ ),
+ (
+ "method_multibanco",
+ forms.BooleanField(
+ label=_("Multibanco"),
+ disabled=self.event.currency != "EUR",
+ help_text=_(
+ "Some payment methods might need to be enabled in the settings of your Stripe account "
+ "before they work properly."
+ ),
+ required=False,
+ ),
+ ),
+ (
+ "method_przelewy24",
+ forms.BooleanField(
+ label=_("Przelewy24"),
+ disabled=self.event.currency not in ["EUR", "PLN"],
+ help_text=_(
+ "Some payment methods might need to be enabled in the settings of your Stripe account "
+ "before they work properly."
+ ),
+ required=False,
+ ),
+ ),
+ (
+ "method_wechatpay",
+ forms.BooleanField(
+ label=_("WeChat Pay"),
+ disabled=self.event.currency
+ not in ["AUD", "CAD", "EUR", "GBP", "HKD", "JPY", "SGD", "USD"],
+ help_text=_(
+ "Some payment methods might need to be enabled in the settings of your Stripe account "
+ "before they work properly."
+ ),
+ required=False,
+ ),
+ ),
+ (
+ "method_revolut_pay",
+ forms.BooleanField(
+ label="Revolut Pay",
+ disabled=self.event.currency not in ["EUR", "GBP"],
+ help_text=_(
+ "Some payment methods might need to be enabled in the settings of your Stripe account "
+ "before they work properly."
+ ),
+ required=False,
+ ),
+ ),
+ (
+ "method_swish",
+ forms.BooleanField(
+ label=_("Swish"),
+ disabled=self.event.currency != "SEK",
+ help_text=_(
+ "Some payment methods might need to be enabled in the settings of your Stripe account "
+ "before they work properly."
+ ),
+ required=False,
+ ),
+ ),
+ (
+ "method_twint",
+ forms.BooleanField(
+ label="TWINT",
+ disabled=self.event.currency != "CHF",
+ help_text=_(
+ "Some payment methods might need to be enabled in the settings of your Stripe account "
+ "before they work properly."
+ ),
+ required=False,
+ ),
+ ),
+ (
+ "method_affirm",
+ forms.BooleanField(
+ label=_("Affirm"),
+ disabled=self.event.currency not in ["USD", "CAD"],
+ help_text=" ".join(
+ [
+ str(
+ _(
+ "Some payment methods might need to be enabled in the settings of your Stripe account "
+ "before they work properly."
+ )
+ ),
+ str(
+ _(
+ "Only available for payments between $50 and $30,000."
+ )
+ ),
+ ]
+ ),
+ required=False,
+ ),
+ ),
+ (
+ "method_klarna",
+ forms.BooleanField(
+ label=_("Klarna"),
+ disabled=self.event.currency
+ not in [
+ "AUD",
+ "CAD",
+ "CHF",
+ "CZK",
+ "DKK",
+ "EUR",
+ "GBP",
+ "NOK",
+ "NZD",
+ "PLN",
+ "SEK",
+ "USD",
+ ],
+ help_text=" ".join(
+ [
+ str(
+ _(
+ "Some payment methods might need to be enabled in the settings of your Stripe account "
+ "before they work properly."
+ )
+ ),
+ str(
+ _(
+ "Klarna and Stripe will decide which of the payment methods offered by Klarna are "
+ "available to the user."
+ )
+ ),
+ str(
+ _(
+ "Klarna's terms of services do not allow it to be used by charities or political "
+ "organizations."
+ )
+ ),
+ ]
+ ),
+ required=False,
+ ),
+ ),
# Disabled for now, since we still need to figure out how to make this work on our connect platform
# ('method_paypal',
# forms.BooleanField(
@@ -482,50 +622,60 @@ class StripeSettingsHolder(BasePaymentProvider):
# 'before they work properly.'),
# required=False,
# )),
- ('method_mobilepay',
- forms.BooleanField(
- label=_('MobilePay'),
- disabled=self.event.currency not in ['DKK', 'EUR', 'NOK', 'SEK'],
- help_text=_('Some payment methods might need to be enabled in the settings of your Stripe account '
- 'before they work properly.'),
- required=False,
- )),
- ] + extra_fields + list(super().settings_form_fields.items()) + moto_settings
+ (
+ "method_mobilepay",
+ forms.BooleanField(
+ label=_("MobilePay"),
+ disabled=self.event.currency
+ not in ["DKK", "EUR", "NOK", "SEK"],
+ help_text=_(
+ "Some payment methods might need to be enabled in the settings of your Stripe account "
+ "before they work properly."
+ ),
+ required=False,
+ ),
+ ),
+ ]
+ + extra_fields
+ + list(super().settings_form_fields.items())
+ + moto_settings
)
if not self.settings.connect_client_id or self.settings.secret_key:
- d['connect_destination'] = forms.CharField(
- label=_('Destination'),
- validators=(
- StripeKeyValidator(['acct_']),
- ),
- required=False
+ d["connect_destination"] = forms.CharField(
+ label=_("Destination"),
+ validators=(StripeKeyValidator(["acct_"]),),
+ required=False,
)
- d.move_to_end('_enabled', last=False)
+ d.move_to_end("_enabled", last=False)
return d
class StripeMethod(BasePaymentProvider):
- identifier = ''
- method = ''
- redirect_action_handling = 'iframe' # or redirect
+ identifier = ""
+ method = ""
+ redirect_action_handling = "iframe" # or redirect
redirect_in_widget_allowed = True
- confirmation_method = 'manual'
- explanation = ''
+ confirmation_method = "manual"
+ explanation = ""
def __init__(self, event: Event):
super().__init__(event)
- self.settings = SettingsSandbox('payment', 'stripe', event)
+ self.settings = SettingsSandbox("payment", "stripe", event)
@property
def test_mode_message(self):
if self.settings.connect_client_id and not self.settings.secret_key:
is_testmode = True
else:
- is_testmode = self.settings.secret_key and '_test_' in self.settings.secret_key
+ is_testmode = (
+ self.settings.secret_key and "_test_" in self.settings.secret_key
+ )
if is_testmode:
return mark_safe(
- _('The Stripe plugin is operating in test mode. You can use one of many test '
- 'cards to perform a transaction. No money will actually be transferred.').format(
+ _(
+ "The Stripe plugin is operating in test mode. You can use one of many test "
+ "cards to perform a transaction. No money will actually be transferred."
+ ).format(
args='href="https://stripe.com/docs/testing#cards" target="_blank"'
)
)
@@ -537,8 +687,9 @@ class StripeMethod(BasePaymentProvider):
@property
def is_enabled(self) -> bool:
- return self.settings.get('_enabled', as_type=bool) and self.settings.get('method_{}'.format(self.method),
- as_type=bool)
+ return self.settings.get("_enabled", as_type=bool) and self.settings.get(
+ "method_{}".format(self.method), as_type=bool
+ )
def payment_refund_supported(self, payment: OrderPayment) -> bool:
return True
@@ -551,81 +702,103 @@ class StripeMethod(BasePaymentProvider):
def _amount_to_decimal(self, cents):
places = settings.CURRENCY_PLACES.get(self.event.currency, 2)
- return round_decimal(float(cents) / (10 ** places), self.event.currency)
+ return round_decimal(float(cents) / (10**places), self.event.currency)
def _decimal_to_int(self, amount):
places = settings.CURRENCY_PLACES.get(self.event.currency, 2)
- return int(amount * 10 ** places)
+ return int(amount * 10**places)
def _get_amount(self, payment):
return self._decimal_to_int(payment.amount)
def _connect_kwargs(self, payment):
d = {}
- if self.settings.connect_client_id and self.settings.connect_user_id and not self.settings.secret_key:
- fee = Decimal('0.00')
- if self.settings.get('connect_app_fee_percent', as_type=Decimal):
- fee = round_decimal(self.settings.get('connect_app_fee_percent', as_type=Decimal) * payment.amount / Decimal('100.00'), self.event.currency)
+ if (
+ self.settings.connect_client_id
+ and self.settings.connect_user_id
+ and not self.settings.secret_key
+ ):
+ fee = Decimal("0.00")
+ if self.settings.get("connect_app_fee_percent", as_type=Decimal):
+ fee = round_decimal(
+ self.settings.get("connect_app_fee_percent", as_type=Decimal)
+ * payment.amount
+ / Decimal("100.00"),
+ self.event.currency,
+ )
if self.settings.connect_app_fee_max:
- fee = min(fee, self.settings.get('connect_app_fee_max', as_type=Decimal))
- if self.settings.get('connect_app_fee_min', as_type=Decimal):
- fee = max(fee, self.settings.get('connect_app_fee_min', as_type=Decimal))
+ fee = min(
+ fee, self.settings.get("connect_app_fee_max", as_type=Decimal)
+ )
+ if self.settings.get("connect_app_fee_min", as_type=Decimal):
+ fee = max(
+ fee, self.settings.get("connect_app_fee_min", as_type=Decimal)
+ )
if fee:
- d['application_fee_amount'] = self._decimal_to_int(fee)
+ d["application_fee_amount"] = self._decimal_to_int(fee)
if self.settings.connect_destination:
- d['transfer_data'] = {
- 'destination': self.settings.connect_destination
- }
+ d["transfer_data"] = {"destination": self.settings.connect_destination}
return d
def statement_descriptor(self, payment, length=22):
if self.settings.postfix:
# If a custom postfix is set, we only transmit the order code, so we have as much room as possible for
# the postfix.
- return '{code} {postfix}'.format(
+ return "{code} {postfix}".format(
code=payment.order.code,
- postfix=re.sub("[^a-zA-Z0-9-_. ]", "", unidecode(str(self.settings.postfix))),
+ postfix=re.sub(
+ "[^a-zA-Z0-9-_. ]", "", unidecode(str(self.settings.postfix))
+ ),
)[:length]
else:
# If no custom postfix is set, we transmit the event slug and event name for backwards compatibility
# with older pretix versions.
- return '{event}-{code} {eventname}'.format(
+ return "{event}-{code} {eventname}".format(
event=self.event.slug.upper(),
code=payment.order.code,
- eventname=re.sub("[^a-zA-Z0-9-_. ]", "", unidecode(str(self.event.name))),
+ eventname=re.sub(
+ "[^a-zA-Z0-9-_. ]", "", unidecode(str(self.event.name))
+ ),
)[:length]
@property
- def api_kwargs(self):
- if self.settings.connect_client_id and self.settings.connect_user_id and not self.settings.secret_key:
- if self.settings.get('endpoint', 'live') == 'live' and not self.event.testmode:
- kwargs = {
- 'api_key': self.settings.connect_secret_key,
- 'stripe_account': self.settings.connect_user_id
- }
- else:
- kwargs = {
- 'api_key': self.settings.connect_test_secret_key,
- 'stripe_account': self.settings.connect_user_id
- }
+ def api_options(self):
+ if (
+ self.settings.connect_client_id
+ and self.settings.connect_user_id
+ and not self.settings.secret_key
+ ):
+ options = {"stripe_account": self.settings.connect_user_id}
else:
- kwargs = {
- 'api_key': self.settings.secret_key,
- }
- return kwargs
+ options = {}
+ return options
def _init_api(self):
- stripe.api_version = '2023-10-16'
- stripe.set_app_info(
- "pretix",
- partner_id="pp_partner_FSaz4PpKIur7Ox",
- version=__version__,
- url="https://pretix.eu"
- )
+ if (
+ self.settings.connect_client_id
+ and self.settings.connect_user_id
+ and not self.settings.secret_key
+ ):
+ if (
+ self.settings.get("endpoint", "live") == "live"
+ and not self.event.testmode
+ ):
+ self.stripe_client = get_stripe_client(self.settings.connect_secret_key)
+ else:
+ self.stripe_client = get_stripe_client(
+ self.settings.connect_test_secret_key
+ )
+ else:
+ self.stripe_client = get_stripe_client(self.settings.secret_key)
def checkout_confirm_render(self, request, **kwargs) -> str:
- template = get_template('pretixplugins/stripe/checkout_payment_confirm.html')
- ctx = {'request': request, 'event': self.event, 'settings': self.settings, 'provider': self}
+ template = get_template("pretixplugins/stripe/checkout_payment_confirm.html")
+ ctx = {
+ "request": request,
+ "event": self.event,
+ "settings": self.settings,
+ "provider": self,
+ }
return template.render(ctx)
def payment_pending_render(self, request, payment) -> str:
@@ -633,16 +806,16 @@ class StripeMethod(BasePaymentProvider):
payment_info = json.loads(payment.info)
else:
payment_info = None
- template = get_template('pretixplugins/stripe/pending.html')
+ template = get_template("pretixplugins/stripe/pending.html")
ctx = {
- 'request': request,
- 'event': self.event,
- 'settings': self.settings,
- 'provider': self,
- 'order': payment.order,
- 'payment': payment,
- 'payment_info': payment_info,
- 'payment_hash': payment.order.tagged_secret('plugins:stripe')
+ "request": request,
+ "event": self.event,
+ "settings": self.settings,
+ "provider": self,
+ "order": payment.order,
+ "payment": payment,
+ "payment_info": payment_info,
+ "payment_hash": payment.order.tagged_secret("plugins:stripe"),
}
return template.render(ctx)
@@ -650,12 +823,12 @@ class StripeMethod(BasePaymentProvider):
return payment.info_data.get("id", None)
def refund_matching_id(self, refund: OrderRefund):
- return refund.info_data.get('id', None)
+ return refund.info_data.get("id", None)
def api_payment_details(self, payment: OrderPayment):
return {
"id": payment.info_data.get("id", None),
- "payment_method": payment.info_data.get("payment_method", None)
+ "payment_method": payment.info_data.get("payment_method", None),
}
def api_refund_details(self, refund: OrderRefund):
@@ -670,41 +843,53 @@ class StripeMethod(BasePaymentProvider):
details = {}
if payment.info:
payment_info = json.loads(payment.info)
- if 'amount' in payment_info:
- payment_info['amount'] /= 10 ** settings.CURRENCY_PLACES.get(self.event.currency, 2)
+ if "amount" in payment_info:
+ payment_info["amount"] /= 10 ** settings.CURRENCY_PLACES.get(
+ self.event.currency, 2
+ )
if isinstance(payment_info.get("latest_charge"), dict):
- details = payment_info["latest_charge"].get("payment_method_details", {})
+ details = payment_info["latest_charge"].get(
+ "payment_method_details", {}
+ )
elif payment_info.get("charges") and payment_info["charges"]["data"]:
- details = payment_info["charges"]["data"][0].get("payment_method_details", {})
+ details = payment_info["charges"]["data"][0].get(
+ "payment_method_details", {}
+ )
elif payment_info.get("source"):
details = payment_info["source"]
else:
payment_info = None
- details.setdefault('owner', {})
+ details.setdefault("owner", {})
- template = get_template('pretixplugins/stripe/control.html')
+ template = get_template("pretixplugins/stripe/control.html")
ctx = {
- 'request': request,
- 'event': self.event,
- 'settings': self.settings,
- 'payment_info': payment_info,
- 'payment': payment,
- 'method': self.method,
- 'details': details,
- 'provider': self,
+ "request": request,
+ "event": self.event,
+ "settings": self.settings,
+ "payment_info": payment_info,
+ "payment": payment,
+ "method": self.method,
+ "details": details,
+ "provider": self,
}
return template.render(ctx)
def redirect(self, request, url):
- if request.session.get('iframe_session', False):
+ if request.session.get("iframe_session", False):
return (
- build_absolute_uri(request.event, 'plugins:stripe:redirect') +
- '?data=' + signing.dumps({
- 'url': url,
- 'session': {
- 'payment_stripe_order_secret': request.session['payment_stripe_order_secret'],
+ build_absolute_uri(request.event, "plugins:stripe:redirect")
+ + "?data="
+ + signing.dumps(
+ {
+ "url": url,
+ "session": {
+ "payment_stripe_order_secret": request.session[
+ "payment_stripe_order_secret"
+ ],
+ },
},
- }, salt='safe-redirect')
+ salt="safe-redirect",
+ )
)
else:
return str(url)
@@ -717,54 +902,68 @@ class StripeMethod(BasePaymentProvider):
OrderPayment.objects.select_for_update(of=OF_SELF).get(pk=refund.payment.pk)
if not payment_info:
- raise PaymentException(_('No payment information found.'))
+ raise PaymentException(_("No payment information found."))
try:
- if payment_info['id'].startswith('pi_'):
- if 'latest_charge' in payment_info and isinstance(payment_info.get("latest_charge"), dict):
- chargeid = payment_info['latest_charge']['id']
+ if payment_info["id"].startswith("pi_"):
+ if "latest_charge" in payment_info and isinstance(
+ payment_info.get("latest_charge"), dict
+ ):
+ chargeid = payment_info["latest_charge"]["id"]
else:
- chargeid = payment_info['charges']['data'][0]['id']
+ chargeid = payment_info["charges"]["data"][0]["id"]
else:
- chargeid = payment_info['id']
+ chargeid = payment_info["id"]
kwargs = {}
if self.settings.connect_destination:
- kwargs['reverse_transfer'] = True
- r = stripe.Refund.create(
- charge=chargeid,
- amount=self._get_amount(refund),
- **self.api_kwargs,
- **kwargs,
+ kwargs["reverse_transfer"] = True
+ r = self.stripe_client.refunds.create(
+ params={
+ "charge": chargeid,
+ "amount": self._get_amount(refund),
+ **kwargs,
+ },
+ options=self.api_options,
)
- except (stripe.error.InvalidRequestError, stripe.error.AuthenticationError, stripe.error.APIConnectionError) \
- as e:
- if e.json_body and 'error' in e.json_body:
- err = e.json_body['error']
- logger.exception('Stripe error: %s' % str(err))
+ except (
+ stripe.error.InvalidRequestError,
+ stripe.error.AuthenticationError,
+ stripe.error.APIConnectionError,
+ ) as e:
+ if e.json_body and "error" in e.json_body:
+ err = e.json_body["error"]
+ logger.exception("Stripe error: %s" % str(err))
else:
- err = {'message': str(e)}
- logger.exception('Stripe error: %s' % str(e))
+ err = {"message": str(e)}
+ logger.exception("Stripe error: %s" % str(e))
refund.info_data = err
refund.state = OrderRefund.REFUND_STATE_FAILED
refund.execution_date = now()
refund.save()
- refund.order.log_action('pretix.event.order.refund.failed', {
- 'local_id': refund.local_id,
- 'provider': refund.provider,
- 'error': str(e)
- })
- raise PaymentException(_('We had trouble communicating with Stripe. Please try again and contact '
- 'support if the problem persists.'))
+ refund.order.log_action(
+ "pretix.event.order.refund.failed",
+ {
+ "local_id": refund.local_id,
+ "provider": refund.provider,
+ "error": str(e),
+ },
+ )
+ raise PaymentException(
+ _(
+ "We had trouble communicating with Stripe. Please try again and contact "
+ "support if the problem persists."
+ )
+ )
except stripe.error.StripeError as err:
- logger.error('Stripe error: %s' % str(err))
- raise PaymentException(_('Stripe returned an error'))
+ logger.error("Stripe error: %s" % str(err))
+ raise PaymentException(_("Stripe returned an error"))
else:
refund.info = str(r)
- if r.status in ('succeeded', 'pending'):
+ if r.status in ("succeeded", "pending"):
refund.done()
- elif r.status in ('failed', 'canceled'):
+ elif r.status in ("failed", "canceled"):
refund.state = OrderRefund.REFUND_STATE_FAILED
refund.execution_date = now()
refund.save()
@@ -775,63 +974,116 @@ class StripeMethod(BasePaymentProvider):
d = json.loads(obj.info)
keys = (
- 'amount', 'currency', 'status', 'id', 'amount_capturable', 'amount_details', 'amount_received',
- 'application', 'application_fee_amount', 'canceled_at', 'confirmation_method', 'created', 'description',
- 'last_payment_error', 'payment_method', 'statement_descriptor', 'livemode'
+ "amount",
+ "currency",
+ "status",
+ "id",
+ "amount_capturable",
+ "amount_details",
+ "amount_received",
+ "application",
+ "application_fee_amount",
+ "canceled_at",
+ "confirmation_method",
+ "created",
+ "description",
+ "last_payment_error",
+ "payment_method",
+ "statement_descriptor",
+ "livemode",
)
new = {k: v for k, v in d.items() if k in keys}
if d.get("latest_charge") and not isinstance(d["latest_charge"], str):
keys = (
- 'amount', 'amount_captured', 'amount_refunded', 'application', 'application_fee_amount',
- 'balance_transaction', 'captured', 'created', 'currency', 'description', 'destination',
- 'disputed', 'failure_balance_transaction', 'failure_code', 'failure_message', 'id',
- 'livemode', 'metadata', 'object', 'on_behalf_of', 'outcome', 'paid', 'payment_intent',
- 'payment_method', 'receipt_url', 'refunded', 'status', 'transfer_data', 'transfer_group',
+ "amount",
+ "amount_captured",
+ "amount_refunded",
+ "application",
+ "application_fee_amount",
+ "balance_transaction",
+ "captured",
+ "created",
+ "currency",
+ "description",
+ "destination",
+ "disputed",
+ "failure_balance_transaction",
+ "failure_code",
+ "failure_message",
+ "id",
+ "livemode",
+ "metadata",
+ "object",
+ "on_behalf_of",
+ "outcome",
+ "paid",
+ "payment_intent",
+ "payment_method",
+ "receipt_url",
+ "refunded",
+ "status",
+ "transfer_data",
+ "transfer_group",
)
- new["latest_charge"] = {k: v for k, v in d["latest_charge"].items() if k in keys}
-
- if d.get('source'):
- new['source'] = {
- 'id': d['source'].get('id'),
- 'type': d['source'].get('type'),
- 'brand': d['source'].get('brand'),
- 'last4': d['source'].get('last4'),
- 'bank_name': d['source'].get('bank_name'),
- 'bank': d['source'].get('bank'),
- 'bic': d['source'].get('bic'),
- 'card': {
- 'brand': d['source'].get('card', {}).get('brand'),
- 'country': d['source'].get('card', {}).get('country'),
- 'last4': d['source'].get('card', {}).get('last4'),
- }
+ new["latest_charge"] = {
+ k: v for k, v in d["latest_charge"].items() if k in keys
}
- new['_shredded'] = True
- obj.info = json.dumps(new)
- obj.save(update_fields=['info'])
+ if d.get("source"):
+ new["source"] = {
+ "id": d["source"].get("id"),
+ "type": d["source"].get("type"),
+ "brand": d["source"].get("brand"),
+ "last4": d["source"].get("last4"),
+ "bank_name": d["source"].get("bank_name"),
+ "bank": d["source"].get("bank"),
+ "bic": d["source"].get("bic"),
+ "card": {
+ "brand": d["source"].get("card", {}).get("brand"),
+ "country": d["source"].get("card", {}).get("country"),
+ "last4": d["source"].get("card", {}).get("last4"),
+ },
+ }
- for le in obj.order.all_logentries().filter(
- action_type="pretix.plugins.stripe.event"
- ).exclude(data="", shredded=True):
+ new["_shredded"] = True
+ obj.info = json.dumps(new)
+ obj.save(update_fields=["info"])
+
+ for le in (
+ obj.order.all_logentries()
+ .filter(action_type="pretix.plugins.stripe.event")
+ .exclude(data="", shredded=True)
+ ):
d = le.parsed_data
- if 'data' in d:
- for k, v in list(d['data']['object'].items()):
- if v not in ('reason', 'status', 'failure_message', 'object', 'id'):
- d['data']['object'][k] = '█'
+ if "data" in d:
+ for k, v in list(d["data"]["object"].items()):
+ if v not in ("reason", "status", "failure_message", "object", "id"):
+ d["data"]["object"][k] = "█"
le.data = json.dumps(d)
le.shredded = True
- le.save(update_fields=['data', 'shredded'])
+ le.save(update_fields=["data", "shredded"])
def payment_is_valid_session(self, request):
- return request.session.get('payment_stripe_{}_payment_method_id'.format(self.method), '') != ''
+ return (
+ request.session.get(
+ "payment_stripe_{}_payment_method_id".format(self.method), ""
+ )
+ != ""
+ )
def checkout_prepare(self, request, cart):
- payment_method_id = request.POST.get('stripe_{}_payment_method_id'.format(self.method), '')
- request.session['payment_stripe_{}_payment_method_id'.format(self.method)] = payment_method_id
+ payment_method_id = request.POST.get(
+ "stripe_{}_payment_method_id".format(self.method), ""
+ )
+ request.session["payment_stripe_{}_payment_method_id".format(self.method)] = (
+ payment_method_id
+ )
- if payment_method_id == '':
- messages.warning(request, _('You may need to enable JavaScript for Stripe payments.'))
+ if payment_method_id == "":
+ messages.warning(
+ request, _("You may need to enable JavaScript for Stripe payments.")
+ )
return False
return True
@@ -839,8 +1091,13 @@ class StripeMethod(BasePaymentProvider):
try:
return self._handle_payment_intent(request, payment)
finally:
- if 'payment_stripe_{}_payment_method_id'.format(self.method) in request.session:
- del request.session['payment_stripe_{}_payment_method_id'.format(self.method)]
+ if (
+ "payment_stripe_{}_payment_method_id".format(self.method)
+ in request.session
+ ):
+ del request.session[
+ "payment_stripe_{}_payment_method_id".format(self.method)
+ ]
def is_moto(self, request, payment=None) -> bool:
return False
@@ -853,107 +1110,120 @@ class StripeMethod(BasePaymentProvider):
try:
if self.payment_is_valid_session(request):
- payment_method_id = request.session.get('payment_stripe_{}_payment_method_id'.format(self.method), None)
- idempotency_key_seed = payment_method_id if payment_method_id is not None else payment.full_id
+ payment_method_id = request.session.get(
+ "payment_stripe_{}_payment_method_id".format(self.method), None
+ )
+ # idempotency_key_seed = payment_method_id if payment_method_id is not None else payment.full_id # Fixme?
params = {}
params.update(self._connect_kwargs(payment))
- params.update(self.api_kwargs)
params.update(self._payment_intent_kwargs(request, payment))
if self.is_moto(request, payment):
- params.update({
- 'payment_method_options': {
- 'card': {
- 'moto': True
- }
- }
- })
+ params.update({"payment_method_options": {"card": {"moto": True}}})
if self.method == "card":
- params['statement_descriptor_suffix'] = self.statement_descriptor(payment)
+ params["statement_descriptor_suffix"] = self.statement_descriptor(
+ payment
+ )
else:
- params['statement_descriptor'] = self.statement_descriptor(payment)
+ params["statement_descriptor"] = self.statement_descriptor(payment)
- intent = stripe.PaymentIntent.create(
- amount=self._get_amount(payment),
- currency=self.event.currency.lower(),
- payment_method=payment_method_id,
- payment_method_types=[self.method],
- confirmation_method=self.confirmation_method,
- confirm=True,
- description='{event}-{code}'.format(
- event=self.event.slug.upper(),
- code=payment.order.code
- ),
- metadata={
- 'order': str(payment.order.id),
- 'event': self.event.id,
- 'code': payment.order.code
+ intent = self.stripe_client.payment_intents.create(
+ params={
+ "amount": self._get_amount(payment),
+ "currency": self.event.currency.lower(),
+ "payment_method": payment_method_id,
+ "payment_method_types": [self.method],
+ "confirmation_method": self.confirmation_method,
+ "confirm": True,
+ "description": "{event}-{code}".format(
+ event=self.event.slug.upper(), code=payment.order.code
+ ),
+ "metadata": {
+ "order": str(payment.order.id),
+ "event": self.event.id,
+ "code": payment.order.code,
+ },
+ "return_url": build_absolute_uri(
+ self.event,
+ "plugins:stripe:sca.return",
+ kwargs={
+ "order": payment.order.code,
+ "payment": payment.pk,
+ "hash": payment.order.tagged_secret("plugins:stripe"),
+ },
+ ),
+ "expand": ["latest_charge"],
+ **params,
},
- # TODO: Is this sufficient?
- idempotency_key=str(self.event.id) + payment.order.code + idempotency_key_seed,
- return_url=build_absolute_uri(self.event, 'plugins:stripe:sca.return', kwargs={
- 'order': payment.order.code,
- 'payment': payment.pk,
- 'hash': payment.order.tagged_secret('plugins:stripe'),
- }),
- expand=['latest_charge'],
- **params
+ options=self.api_options,
)
else:
payment_info = json.loads(payment.info)
- if 'id' in payment_info:
+ if "id" in payment_info:
if not intent:
- intent = stripe.PaymentIntent.retrieve(
- payment_info['id'],
- expand=["latest_charge"],
- **self.api_kwargs
+ intent = self.stripe_client.payment_intents.retrieve(
+ payment_info["id"],
+ params={
+ "expand": ["latest_charge"],
+ },
+ options=self.api_options,
)
else:
return
except stripe.error.CardError as e:
if e.json_body:
- err = e.json_body['error']
- logger.exception('Stripe error: %s' % str(err))
+ err = e.json_body["error"]
+ logger.exception("Stripe error: %s" % str(err))
else:
- err = {'message': str(e)}
- logger.exception('Stripe error: %s' % str(e))
- logger.info('Stripe card error: %s' % str(err))
- payment.fail(info={
- 'error': True,
- 'message': err['message'],
- })
- raise PaymentException(_('Stripe reported an error with your card: %s') % err['message'])
+ err = {"message": str(e)}
+ logger.exception("Stripe error: %s" % str(e))
+ logger.info("Stripe card error: %s" % str(err))
+ payment.fail(
+ info={
+ "error": True,
+ "message": err["message"],
+ }
+ )
+ raise PaymentException(
+ _("Stripe reported an error with your card: %s") % err["message"]
+ )
except stripe.error.StripeError as e:
- if e.json_body and 'error' in e.json_body:
- err = e.json_body['error']
- logger.exception('Stripe error: %s' % str(err))
+ if e.json_body and "error" in e.json_body:
+ err = e.json_body["error"]
+ logger.exception("Stripe error: %s" % str(err))
- if err.get('code') == 'idempotency_key_in_use':
+ if err.get("code") == "idempotency_key_in_use":
# Same thing happening twice – we don't want to record a failure, as that might prevent the
# other thread from succeeding.
return
else:
- err = {'message': str(e)}
- logger.exception('Stripe error: %s' % str(e))
- payment.fail(info={
- 'error': True,
- 'message': err['message'],
- })
- raise PaymentException(_('We had trouble communicating with Stripe. Please try again and get in touch '
- 'with us if this problem persists.'))
+ err = {"message": str(e)}
+ logger.exception("Stripe error: %s" % str(e))
+ payment.fail(
+ info={
+ "error": True,
+ "message": err["message"],
+ }
+ )
+ raise PaymentException(
+ _(
+ "We had trouble communicating with Stripe. Please try again and get in touch "
+ "with us if this problem persists."
+ )
+ )
else:
ReferencedStripeObject.objects.get_or_create(
reference=intent.id,
- defaults={'order': payment.order, 'payment': payment}
+ defaults={"order": payment.order, "payment": payment},
)
- if intent.status == 'requires_action':
+ if intent.status == "requires_action":
payment.info = str(intent)
- if intent.next_action.type == 'multibanco_display_details':
+ if intent.next_action.type == "multibanco_display_details":
payment.state = OrderPayment.PAYMENT_STATE_PENDING
payment.save()
return
@@ -962,19 +1232,19 @@ class StripeMethod(BasePaymentProvider):
payment.save()
return self._redirect_to_sca(request, payment)
- if intent.status == 'requires_action':
+ if intent.status == "requires_action":
payment.info = str(intent)
payment.state = OrderPayment.PAYMENT_STATE_CREATED
payment.save()
return self._redirect_to_sca(request, payment)
- if intent.status == 'requires_confirmation':
+ if intent.status == "requires_confirmation":
payment.info = str(intent)
payment.state = OrderPayment.PAYMENT_STATE_CREATED
payment.save()
self._confirm_payment_intent(request, payment)
- elif intent.status == 'succeeded' and intent.latest_charge.paid:
+ elif intent.status == "succeeded" and intent.latest_charge.paid:
try:
payment.info = str(intent)
payment.confirm()
@@ -982,36 +1252,61 @@ class StripeMethod(BasePaymentProvider):
raise PaymentException(str(e))
except SendMailException:
- raise PaymentException(_('There was an error sending the confirmation mail.'))
- elif intent.status == 'processing':
+ raise PaymentException(
+ _("There was an error sending the confirmation mail.")
+ )
+ elif intent.status == "processing":
if request:
- messages.warning(request, _('Your payment is pending completion. We will inform you as soon as the '
- 'payment completed.'))
+ messages.warning(
+ request,
+ _(
+ "Your payment is pending completion. We will inform you as soon as the "
+ "payment completed."
+ ),
+ )
payment.info = str(intent)
payment.state = OrderPayment.PAYMENT_STATE_PENDING
payment.save()
return
- elif intent.status == 'requires_payment_method':
+ elif intent.status == "requires_payment_method":
if request:
- messages.warning(request, _('Your payment failed. Please try again.'))
+ messages.warning(
+ request, _("Your payment failed. Please try again.")
+ )
payment.fail(info=str(intent))
return
else:
- logger.info('Charge failed: %s' % str(intent))
+ logger.info("Charge failed: %s" % str(intent))
payment.fail(info=str(intent))
- raise PaymentException(_('Stripe reported an error: %s') % intent.last_payment_error.message)
+ raise PaymentException(
+ _("Stripe reported an error: %s")
+ % intent.last_payment_error.message
+ )
def _redirect_to_sca(self, request, payment):
- url = build_absolute_uri(self.event, 'plugins:stripe:sca', kwargs={
- 'order': payment.order.code,
- 'payment': payment.pk,
- 'hash': payment.order.tagged_secret('plugins:stripe'),
- })
- if not self.redirect_in_widget_allowed and request.session.get('iframe_session', False):
- return build_absolute_uri(self.event, 'plugins:stripe:redirect') + '?data=' + signing.dumps({
- 'url': url,
- 'session': {},
- }, salt='safe-redirect')
+ url = build_absolute_uri(
+ self.event,
+ "plugins:stripe:sca",
+ kwargs={
+ "order": payment.order.code,
+ "payment": payment.pk,
+ "hash": payment.order.tagged_secret("plugins:stripe"),
+ },
+ )
+ if not self.redirect_in_widget_allowed and request.session.get(
+ "iframe_session", False
+ ):
+ return (
+ build_absolute_uri(self.event, "plugins:stripe:redirect")
+ + "?data="
+ + signing.dumps(
+ {
+ "url": url,
+ "session": {},
+ },
+ salt="safe-redirect",
+ )
+ )
return url
@@ -1021,15 +1316,21 @@ class StripeMethod(BasePaymentProvider):
try:
payment_info = json.loads(payment.info)
- intent = stripe.PaymentIntent.confirm(
- payment_info['id'],
- return_url=build_absolute_uri(self.event, 'plugins:stripe:sca.return', kwargs={
- 'order': payment.order.code,
- 'payment': payment.pk,
- 'hash': payment.order.tagged_secret('plugins:stripe'),
- }),
- expand=["latest_charge"],
- **self.api_kwargs
+ intent = self.stripe_client.payment_intents.confirm(
+ payment_info["id"],
+ params={
+ "return_url": build_absolute_uri(
+ self.event,
+ "plugins:stripe:sca.return",
+ kwargs={
+ "order": payment.order.code,
+ "payment": payment.pk,
+ "hash": payment.order.tagged_secret("plugins:stripe"),
+ },
+ ),
+ "expand": ["latest_charge"],
+ },
+ options=self.api_options,
)
payment.info = str(intent)
@@ -1038,30 +1339,40 @@ class StripeMethod(BasePaymentProvider):
self._handle_payment_intent(request, payment)
except stripe.error.CardError as e:
if e.json_body:
- err = e.json_body['error']
- logger.exception('Stripe error: %s' % str(err))
+ err = e.json_body["error"]
+ logger.exception("Stripe error: %s" % str(err))
else:
- err = {'message': str(e)}
- logger.exception('Stripe error: %s' % str(e))
- logger.info('Stripe card error: %s' % str(err))
- payment.fail(info={
- 'error': True,
- 'message': err['message'],
- })
- raise PaymentException(_('Stripe reported an error with your card: %s') % err['message'])
+ err = {"message": str(e)}
+ logger.exception("Stripe error: %s" % str(e))
+ logger.info("Stripe card error: %s" % str(err))
+ payment.fail(
+ info={
+ "error": True,
+ "message": err["message"],
+ }
+ )
+ raise PaymentException(
+ _("Stripe reported an error with your card: %s") % err["message"]
+ )
except stripe.error.InvalidRequestError as e:
if e.json_body:
- err = e.json_body['error']
- logger.exception('Stripe error: %s' % str(err))
+ err = e.json_body["error"]
+ logger.exception("Stripe error: %s" % str(err))
else:
- err = {'message': str(e)}
- logger.exception('Stripe error: %s' % str(e))
- payment.fail(info={
- 'error': True,
- 'message': err['message'],
- })
- raise PaymentException(_('We had trouble communicating with Stripe. Please try again and get in touch '
- 'with us if this problem persists.'))
+ err = {"message": str(e)}
+ logger.exception("Stripe error: %s" % str(e))
+ payment.fail(
+ info={
+ "error": True,
+ "message": err["message"],
+ }
+ )
+ raise PaymentException(
+ _(
+ "We had trouble communicating with Stripe. Please try again and get in touch "
+ "with us if this problem persists."
+ )
+ )
class StripeRedirectMethod(StripeMethod):
@@ -1077,7 +1388,9 @@ class StripeRedirectMethod(StripeMethod):
def checkout_prepare(self, request, cart):
# This does not have a payment_method_id, so we set it manually to None during checkout, so that we can
# verify later on if we are in or outside the checkout process.
- request.session["payment_stripe_{}_payment_method_id".format(self.method)] = None
+ request.session["payment_stripe_{}_payment_method_id".format(self.method)] = (
+ None
+ )
return True
def _payment_intent_kwargs(self, request, payment):
@@ -1088,21 +1401,23 @@ class StripeRedirectMethod(StripeMethod):
}
def payment_form_render(self, request) -> str:
- template = get_template('pretixplugins/stripe/checkout_payment_form_simple_noform.html')
+ template = get_template(
+ "pretixplugins/stripe/checkout_payment_form_simple_noform.html"
+ )
ctx = {
- 'request': request,
- 'event': self.event,
- 'settings': self.settings,
- 'explanation': self.explanation,
+ "request": request,
+ "event": self.event,
+ "settings": self.settings,
+ "explanation": self.explanation,
}
return template.render(ctx)
class StripeCC(StripeMethod):
- identifier = 'stripe'
- verbose_name = _('Credit card via Stripe')
- public_name = _('Credit card')
- method = 'card'
+ identifier = "stripe"
+ verbose_name = _("Credit card via Stripe")
+ public_name = _("Credit card")
+ method = "card"
@property
def walletqueries(self):
@@ -1115,26 +1430,28 @@ class StripeCC(StripeMethod):
def payment_form_render(self, request, total, order=None) -> str:
account = get_stripe_account_key(self)
- if not RegisteredApplePayDomain.objects.filter(account=account, domain=request.host).exists():
+ if not RegisteredApplePayDomain.objects.filter(
+ account=account, domain=request.host
+ ).exists():
stripe_verify_domain.apply_async(args=(self.event.pk, request.host))
- template = get_template('pretixplugins/stripe/checkout_payment_form_card.html')
+ template = get_template("pretixplugins/stripe/checkout_payment_form_card.html")
ctx = {
- 'request': request,
- 'event': self.event,
- 'total': self._decimal_to_int(total),
- 'settings': self.settings,
- 'explanation': self.explanation,
- 'is_moto': self.is_moto(request)
+ "request": request,
+ "event": self.event,
+ "total": self._decimal_to_int(total),
+ "settings": self.settings,
+ "explanation": self.explanation,
+ "is_moto": self.is_moto(request),
}
return template.render(ctx)
def _migrate_session(self, request):
# todo: remove after pretix 2023.8 was released
keymap = {
- 'payment_stripe_payment_method_id': 'payment_stripe_card_payment_method_id',
- 'payment_stripe_brand': 'payment_stripe_card_brand',
- 'payment_stripe_last4': 'payment_stripe_card_last4',
+ "payment_stripe_payment_method_id": "payment_stripe_card_payment_method_id",
+ "payment_stripe_brand": "payment_stripe_card_brand",
+ "payment_stripe_last4": "payment_stripe_card_last4",
}
for old, new in keymap.items():
if old in request.session:
@@ -1143,8 +1460,12 @@ class StripeCC(StripeMethod):
def checkout_prepare(self, request, cart):
self._migrate_session(request)
- request.session['payment_stripe_card_brand'] = request.POST.get('stripe_card_brand', '')
- request.session['payment_stripe_card_last4'] = request.POST.get('stripe_card_last4', '')
+ request.session["payment_stripe_card_brand"] = request.POST.get(
+ "stripe_card_brand", ""
+ )
+ request.session["payment_stripe_card_last4"] = request.POST.get(
+ "stripe_card_last4", ""
+ )
return super().checkout_prepare(request, cart)
@@ -1160,13 +1481,15 @@ class StripeCC(StripeMethod):
# We don't have a payment yet when checking if we should display the MOTO-flag
# However, before we execute the payment, we absolutely have to check if the request-SalesChannel as well as the
# order are tagged as a reseller-transaction. Else, a user with a valid reseller-session might be able to place
- # a MOTO transaction trough the WebShop.
+ # a MOTO transaction through the WebShop.
- moto = self.settings.get('reseller_moto', False, as_type=bool) and \
- request.sales_channel.identifier == 'resellers'
+ moto = (
+ self.settings.get("reseller_moto", False, as_type=bool)
+ and request.sales_channel.identifier == "resellers"
+ )
if payment:
- return moto and payment.order.sales_channel.identifier == 'resellers'
+ return moto and payment.order.sales_channel.identifier == "resellers"
return moto
@@ -1178,33 +1501,39 @@ class StripeCC(StripeMethod):
else:
card = pi["source"]["card"]
except:
- logger.exception('Could not parse payment data')
+ 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"))}'
+ 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 StripeSEPADirectDebit(StripeMethod):
- identifier = 'stripe_sepa_debit'
- verbose_name = _('SEPA Debit via Stripe')
- public_name = _('SEPA Debit')
- method = 'sepa_debit'
+ identifier = "stripe_sepa_debit"
+ verbose_name = _("SEPA Debit via Stripe")
+ public_name = _("SEPA Debit")
+ method = "sepa_debit"
ia = InvoiceAddress()
- def payment_form_render(self, request: HttpRequest, total: Decimal, order: Order=None) -> str:
+ def payment_form_render(
+ self, request: HttpRequest, total: Decimal, order: Order = None
+ ) -> str:
def get_invoice_address():
- if order and getattr(order, 'invoice_address', None):
+ if order and getattr(order, "invoice_address", None):
request._checkout_flow_invoice_address = order.invoice_address
- if not hasattr(request, '_checkout_flow_invoice_address'):
+ if not hasattr(request, "_checkout_flow_invoice_address"):
cs = cart_session(request)
- iapk = cs.get('invoice_address')
+ iapk = cs.get("invoice_address")
if not iapk:
request._checkout_flow_invoice_address = InvoiceAddress()
else:
try:
- request._checkout_flow_invoice_address = InvoiceAddress.objects.get(pk=iapk, order__isnull=True)
+ request._checkout_flow_invoice_address = (
+ InvoiceAddress.objects.get(pk=iapk, order__isnull=True)
+ )
except InvoiceAddress.DoesNotExist:
request._checkout_flow_invoice_address = InvoiceAddress()
return request._checkout_flow_invoice_address
@@ -1212,14 +1541,16 @@ class StripeSEPADirectDebit(StripeMethod):
cs = cart_session(request)
self.ia = get_invoice_address()
- template = get_template('pretixplugins/stripe/checkout_payment_form_sepadirectdebit.html')
+ template = get_template(
+ "pretixplugins/stripe/checkout_payment_form_sepadirectdebit.html"
+ )
ctx = {
- 'request': request,
- 'event': self.event,
- 'settings': self.settings,
- 'form': self.payment_form(request),
- 'explanation': self.explanation,
- 'email': order.email if order else cs.get('email', '')
+ "request": request,
+ "event": self.event,
+ "settings": self.settings,
+ "form": self.payment_form(request),
+ "explanation": self.explanation,
+ "email": order.email if order else cs.get("email", ""),
}
return template.render(ctx)
@@ -1227,78 +1558,93 @@ class StripeSEPADirectDebit(StripeMethod):
def payment_form_fields(self):
return OrderedDict(
[
- ('accountname',
- forms.CharField(
- label=_('Account Holder Name'),
- initial=self.ia.name,
- )),
- ('line1',
- forms.CharField(
- label=_('Account Holder Street'),
- required=False,
- widget=forms.TextInput(
- attrs={
- 'data-display-dependency': '#stripe_sepa_debit_country',
- 'data-required-if': '#stripe_sepa_debit_country'
- }
- ),
- initial=self.ia.street,
- )),
- ('postal_code',
- forms.CharField(
- label=_('Account Holder Postal Code'),
- required=False,
- widget=forms.TextInput(
- attrs={
- 'data-display-dependency': '#stripe_sepa_debit_country',
- 'data-required-if': '#stripe_sepa_debit_country'
- }
- ),
- initial=self.ia.zipcode,
- )),
- ('city',
- forms.CharField(
- label=_('Account Holder City'),
- required=False,
- widget=forms.TextInput(
- attrs={
- 'data-display-dependency': '#stripe_sepa_debit_country',
- 'data-required-if': '#stripe_sepa_debit_country'
- }
- ),
- initial=self.ia.city,
- )),
- ('country',
- forms.ChoiceField(
- label=_('Account Holder Country'),
- required=False,
- choices=CachedCountries(),
- widget=forms.Select(
- attrs={
- 'data-display-dependency': '#stripe_sepa_debit_country',
- 'data-required-if': '#stripe_sepa_debit_country'
- }
- ),
- initial=self.ia.country or guess_country(self.event),
- )),
- ])
+ (
+ "accountname",
+ forms.CharField(
+ label=_("Account Holder Name"),
+ initial=self.ia.name,
+ ),
+ ),
+ (
+ "line1",
+ forms.CharField(
+ label=_("Account Holder Street"),
+ required=False,
+ widget=forms.TextInput(
+ attrs={
+ "data-display-dependency": "#stripe_sepa_debit_country",
+ "data-required-if": "#stripe_sepa_debit_country",
+ }
+ ),
+ initial=self.ia.street,
+ ),
+ ),
+ (
+ "postal_code",
+ forms.CharField(
+ label=_("Account Holder Postal Code"),
+ required=False,
+ widget=forms.TextInput(
+ attrs={
+ "data-display-dependency": "#stripe_sepa_debit_country",
+ "data-required-if": "#stripe_sepa_debit_country",
+ }
+ ),
+ initial=self.ia.zipcode,
+ ),
+ ),
+ (
+ "city",
+ forms.CharField(
+ label=_("Account Holder City"),
+ required=False,
+ widget=forms.TextInput(
+ attrs={
+ "data-display-dependency": "#stripe_sepa_debit_country",
+ "data-required-if": "#stripe_sepa_debit_country",
+ }
+ ),
+ initial=self.ia.city,
+ ),
+ ),
+ (
+ "country",
+ forms.ChoiceField(
+ label=_("Account Holder Country"),
+ required=False,
+ choices=CachedCountries(),
+ widget=forms.Select(
+ attrs={
+ "data-display-dependency": "#stripe_sepa_debit_country",
+ "data-required-if": "#stripe_sepa_debit_country",
+ }
+ ),
+ initial=self.ia.country or guess_country(self.event),
+ ),
+ ),
+ ]
+ )
def _payment_intent_kwargs(self, request, payment):
return {
- 'mandate_data': {
- 'customer_acceptance': {
- 'type': 'online',
- 'online': {
- 'ip_address': get_client_ip(request),
- 'user_agent': request.META['HTTP_USER_AGENT'],
- }
+ "mandate_data": {
+ "customer_acceptance": {
+ "type": "online",
+ "online": {
+ "ip_address": get_client_ip(request),
+ "user_agent": request.META["HTTP_USER_AGENT"],
+ },
},
}
}
def checkout_prepare(self, request, cart):
- request.session['payment_stripe_sepa_debit_last4'] = request.POST.get('stripe_sepa_debit_last4', '')
- request.session['payment_stripe_sepa_debit_bank'] = request.POST.get('stripe_sepa_debit_bank', '')
+ request.session["payment_stripe_sepa_debit_last4"] = request.POST.get(
+ "stripe_sepa_debit_last4", ""
+ )
+ request.session["payment_stripe_sepa_debit_bank"] = request.POST.get(
+ "stripe_sepa_debit_bank", ""
+ )
return super().checkout_prepare(request, cart)
@@ -1306,53 +1652,61 @@ class StripeSEPADirectDebit(StripeMethod):
try:
return super().execute_payment(request, payment)
finally:
- fields = ['accountname', 'line1', 'postal_code', 'city', 'country']
+ fields = ["accountname", "line1", "postal_code", "city", "country"]
for field in fields:
- if 'payment_stripe_sepa_debit_{}'.format(field) in request.session:
- del request.session['payment_stripe_sepa_debit_{}'.format(field)]
+ if "payment_stripe_sepa_debit_{}".format(field) in request.session:
+ del request.session["payment_stripe_sepa_debit_{}".format(field)]
class StripeAffirm(StripeMethod):
- identifier = 'stripe_affirm'
- verbose_name = _('Affirm via Stripe')
- public_name = _('Affirm')
- method = 'affirm'
- redirect_action_handling = 'redirect'
+ identifier = "stripe_affirm"
+ verbose_name = _("Affirm via Stripe")
+ public_name = _("Affirm")
+ method = "affirm"
+ redirect_action_handling = "redirect"
def payment_is_valid_session(self, request):
# Affirm does not have a payment_method_id, so we set it manually to None during checkout.
# But we still need to check for its presence here.
- if 'payment_stripe_{}_payment_method_id'.format(self.method) in request.session:
+ if "payment_stripe_{}_payment_method_id".format(self.method) in request.session:
return True
return False
def checkout_prepare(self, request, cart):
# Affirm does not have a payment_method_id, so we set it manually to None during checkout, so that we can
# verify later on if we are in or outside the checkout process.
- request.session['payment_stripe_{}_payment_method_id'.format(self.method)] = None
+ request.session["payment_stripe_{}_payment_method_id".format(self.method)] = (
+ None
+ )
return True
- def is_allowed(self, request: HttpRequest, total: Decimal=None) -> bool:
- return Decimal(50.00) <= total <= Decimal(30000.00) and super().is_allowed(request, total)
+ def is_allowed(self, request: HttpRequest, total: Decimal = None) -> bool:
+ return Decimal(50.00) <= total <= Decimal(30000.00) and super().is_allowed(
+ request, total
+ )
- def order_change_allowed(self, order: Order, request: HttpRequest=None) -> bool:
- return Decimal(50.00) <= order.pending_sum <= Decimal(30000.00) and super().order_change_allowed(order, request)
+ def order_change_allowed(self, order: Order, request: HttpRequest = None) -> bool:
+ return Decimal(50.00) <= order.pending_sum <= Decimal(
+ 30000.00
+ ) and super().order_change_allowed(order, request)
def _payment_intent_kwargs(self, request, payment):
return {
- 'payment_method_data': {
- 'type': 'affirm',
+ "payment_method_data": {
+ "type": "affirm",
}
}
def payment_form_render(self, request, total, order=None) -> str:
- template = get_template('pretixplugins/stripe/checkout_payment_form_simple_messaging_noform.html')
+ template = get_template(
+ "pretixplugins/stripe/checkout_payment_form_simple_messaging_noform.html"
+ )
ctx = {
- 'request': request,
- 'event': self.event,
- 'total': self._decimal_to_int(total),
- 'explanation': self.explanation,
- 'method': self.method,
+ "request": request,
+ "event": self.event,
+ "total": self._decimal_to_int(total),
+ "explanation": self.explanation,
+ "method": self.method,
}
return template.render(ctx)
@@ -1362,21 +1716,41 @@ class StripeKlarna(StripeRedirectMethod):
verbose_name = _("Klarna via Stripe")
public_name = _("Klarna")
method = "klarna"
- allowed_countries = {"US", "CA", "AU", "NZ", "GB", "IE", "FR", "ES", "DE", "AT", "BE", "DK", "FI", "IT", "NL", "NO", "SE"}
+ allowed_countries = {
+ "US",
+ "CA",
+ "AU",
+ "NZ",
+ "GB",
+ "IE",
+ "FR",
+ "ES",
+ "DE",
+ "AT",
+ "BE",
+ "DK",
+ "FI",
+ "IT",
+ "NL",
+ "NO",
+ "SE",
+ }
redirect_in_widget_allowed = False
def _detect_country(self, request, order=None):
def get_invoice_address():
- if order and getattr(order, 'invoice_address', None):
+ if order and getattr(order, "invoice_address", None):
request._checkout_flow_invoice_address = order.invoice_address
- if not hasattr(request, '_checkout_flow_invoice_address'):
+ if not hasattr(request, "_checkout_flow_invoice_address"):
cs = cart_session(request)
- iapk = cs.get('invoice_address')
+ iapk = cs.get("invoice_address")
if not iapk:
request._checkout_flow_invoice_address = InvoiceAddress()
else:
try:
- request._checkout_flow_invoice_address = InvoiceAddress.objects.get(pk=iapk, order__isnull=True)
+ request._checkout_flow_invoice_address = (
+ InvoiceAddress.objects.get(pk=iapk, order__isnull=True)
+ )
except InvoiceAddress.DoesNotExist:
request._checkout_flow_invoice_address = InvoiceAddress()
return request._checkout_flow_invoice_address
@@ -1415,8 +1789,8 @@ class StripeKlarna(StripeRedirectMethod):
"event": self.event,
"total": self._decimal_to_int(total),
"method": self.method,
- 'explanation': self.explanation,
- "country": self._detect_country(request, order)
+ "explanation": self.explanation,
+ "country": self._detect_country(request, order),
}
return template.render(ctx)
@@ -1442,56 +1816,62 @@ class StripeKlarna(StripeRedirectMethod):
class StripeRedirectWithAccountNamePaymentIntentMethod(StripeRedirectMethod):
def payment_form_render(self, request) -> str:
- template = get_template('pretixplugins/stripe/checkout_payment_form_simple.html')
+ template = get_template(
+ "pretixplugins/stripe/checkout_payment_form_simple.html"
+ )
ctx = {
- 'request': request,
- 'event': self.event,
- 'settings': self.settings,
- 'explanation': self.explanation,
- 'form': self.payment_form(request)
+ "request": request,
+ "event": self.event,
+ "settings": self.settings,
+ "explanation": self.explanation,
+ "form": self.payment_form(request),
}
return template.render(ctx)
@property
def payment_form_fields(self):
- return OrderedDict([
- ('account', forms.CharField(label=_('Account holder'))),
- ])
+ return OrderedDict(
+ [
+ ("account", forms.CharField(label=_("Account holder"))),
+ ]
+ )
def execute_payment(self, request: HttpRequest, payment: OrderPayment):
try:
return super().execute_payment(request, payment)
finally:
- if f'payment_stripe_{self.method}_account' in request.session:
- del request.session[f'payment_stripe_{self.method}_account']
+ if f"payment_stripe_{self.method}_account" in request.session:
+ del request.session[f"payment_stripe_{self.method}_account"]
def checkout_prepare(self, request, cart):
form = self.payment_form(request)
if form.is_valid():
request.session[f"payment_stripe_{self.method}_payment_method_id"] = None
- request.session[f'payment_stripe_{self.method}_account'] = form.cleaned_data['account']
+ request.session[f"payment_stripe_{self.method}_account"] = (
+ form.cleaned_data["account"]
+ )
return True
return False
class StripeGiropay(StripeRedirectWithAccountNamePaymentIntentMethod):
- identifier = 'stripe_giropay'
- verbose_name = _('giropay via Stripe')
- public_name = _('giropay')
- method = 'giropay'
+ identifier = "stripe_giropay"
+ verbose_name = _("giropay via Stripe")
+ public_name = _("giropay")
+ method = "giropay"
explanation = _(
- 'giropay is an online payment method available to all customers of most German banks, usually after one-time '
- 'activation. Please keep your online banking account and login information available.'
+ "giropay is an online payment method available to all customers of most German banks, usually after one-time "
+ "activation. Please keep your online banking account and login information available."
)
redirect_in_widget_allowed = False
- def is_allowed(self, request: HttpRequest, total: Decimal=None) -> bool:
+ def is_allowed(self, request: HttpRequest, total: Decimal = None) -> bool:
# Stripe<>giropay is shut down July 1st
return super().is_allowed(request, total) and now() < datetime(
2024, 7, 1, 0, 0, 0, tzinfo=zoneinfo.ZoneInfo("Europe/Berlin")
)
- def order_change_allowed(self, order: Order, request: HttpRequest=None) -> bool:
+ def order_change_allowed(self, order: Order, request: HttpRequest = None) -> bool:
return super().order_change_allowed(order, request) and now() < datetime(
2024, 7, 1, 0, 0, 0, tzinfo=zoneinfo.ZoneInfo("Europe/Berlin")
)
@@ -1502,7 +1882,8 @@ class StripeGiropay(StripeRedirectWithAccountNamePaymentIntentMethod):
"type": "giropay",
"giropay": {},
"billing_details": {
- "name": request.session.get(f"payment_stripe_{self.method}_account") or gettext("unknown name")
+ "name": request.session.get(f"payment_stripe_{self.method}_account")
+ or gettext("unknown name")
},
}
}
@@ -1510,59 +1891,67 @@ class StripeGiropay(StripeRedirectWithAccountNamePaymentIntentMethod):
def payment_presale_render(self, payment: OrderPayment) -> str:
pi = payment.info_data or {}
try:
- return gettext('Bank account at {bank}').format(
+ return gettext("Bank account at {bank}").format(
bank=(
- pi.get("latest_charge", {}).get("payment_method_details", {}).get("giropay", {}).get("bank_name") or
- pi.get("source", {}).get("giropay", {}).get("bank_name", "?")
+ pi.get("latest_charge", {})
+ .get("payment_method_details", {})
+ .get("giropay", {})
+ .get("bank_name")
+ or pi.get("source", {}).get("giropay", {}).get("bank_name", "?")
)
)
except:
- logger.exception('Could not parse payment data')
+ logger.exception("Could not parse payment data")
return super().payment_presale_render(payment)
class StripeIdeal(StripeRedirectMethod):
- identifier = 'stripe_ideal'
- verbose_name = _('iDEAL via Stripe')
- public_name = _('iDEAL')
- method = 'ideal'
+ identifier = "stripe_ideal"
+ verbose_name = _("iDEAL via Stripe")
+ public_name = _("iDEAL")
+ method = "ideal"
explanation = _(
- 'iDEAL is an online payment method available to customers of Dutch banks. Please keep your online '
- 'banking account and login information available.'
+ "iDEAL is an online payment method available to customers of Dutch banks. Please keep your online "
+ "banking account and login information available."
)
redirect_in_widget_allowed = False
def payment_presale_render(self, payment: OrderPayment) -> str:
pi = payment.info_data or {}
try:
- return gettext('Bank account at {bank}').format(
+ return gettext("Bank account at {bank}").format(
bank=(
- pi.get("latest_charge", {}).get("payment_method_details", {}).get("ideal", {}).get("bank") or
- pi.get("source", {}).get("ideal", {}).get("bank", "?")
- ).replace("_", " ").title()
+ pi.get("latest_charge", {})
+ .get("payment_method_details", {})
+ .get("ideal", {})
+ .get("bank")
+ or pi.get("source", {}).get("ideal", {}).get("bank", "?")
+ )
+ .replace("_", " ")
+ .title()
)
except:
- logger.exception('Could not parse payment data')
+ logger.exception("Could not parse payment data")
return super().payment_presale_render(payment)
class StripeAlipay(StripeRedirectMethod):
- identifier = 'stripe_alipay'
- verbose_name = _('Alipay via Stripe')
- public_name = _('Alipay')
- method = 'alipay'
- confirmation_method = 'automatic'
+ identifier = "stripe_alipay"
+ verbose_name = _("Alipay via Stripe")
+ public_name = _("Alipay")
+ method = "alipay"
+ confirmation_method = "automatic"
explanation = _(
- 'This payment method is available to customers of the Chinese payment system Alipay. Please keep '
- 'your login information available.'
+ "This payment method is available to customers of the Chinese payment system Alipay. Please keep "
+ "your login information available."
)
class StripeBancontact(StripeRedirectWithAccountNamePaymentIntentMethod):
- identifier = 'stripe_bancontact'
- verbose_name = _('Bancontact via Stripe')
- public_name = _('Bancontact')
- method = 'bancontact'
+ identifier = "stripe_bancontact"
+ verbose_name = _("Bancontact via Stripe")
+ public_name = _("Bancontact")
+ method = "bancontact"
redirect_in_widget_allowed = False
def _payment_intent_kwargs(self, request, payment):
@@ -1570,7 +1959,8 @@ class StripeBancontact(StripeRedirectWithAccountNamePaymentIntentMethod):
"payment_method_data": {
"type": "bancontact",
"billing_details": {
- "name": request.session.get(f"payment_stripe_{self.method}_account") or gettext("unknown name")
+ "name": request.session.get(f"payment_stripe_{self.method}_account")
+ or gettext("unknown name")
},
}
}
@@ -1578,64 +1968,82 @@ class StripeBancontact(StripeRedirectWithAccountNamePaymentIntentMethod):
def payment_presale_render(self, payment: OrderPayment) -> str:
pi = payment.info_data or {}
try:
- return gettext('Bank account at {bank}').format(
+ return gettext("Bank account at {bank}").format(
bank=(
- pi.get("latest_charge", {}).get("payment_method_details", {}).get("bancontact", {}).get("bank_name") or
- pi.get("source", {}).get("bancontact", {}).get("bank_name", "?")
+ pi.get("latest_charge", {})
+ .get("payment_method_details", {})
+ .get("bancontact", {})
+ .get("bank_name")
+ or pi.get("source", {}).get("bancontact", {}).get("bank_name", "?")
)
)
except:
- logger.exception('Could not parse payment data')
+ logger.exception("Could not parse payment data")
return super().payment_presale_render(payment)
class StripeSofort(StripeRedirectMethod):
- identifier = 'stripe_sofort'
- verbose_name = _('SOFORT via Stripe')
- public_name = _('SOFORT (instant bank transfer)')
- method = 'sofort'
+ identifier = "stripe_sofort"
+ verbose_name = _("SOFORT via Stripe")
+ public_name = _("SOFORT (instant bank transfer)")
+ method = "sofort"
redirect_in_widget_allowed = False
- def is_allowed(self, request: HttpRequest, total: Decimal=None) -> bool:
+ def is_allowed(self, request: HttpRequest, total: Decimal = None) -> bool:
# Stripe<>Sofort is shut down November 29th
return super().is_allowed(request, total) and now() < datetime(
2024, 11, 29, 0, 0, 0, tzinfo=zoneinfo.ZoneInfo("Europe/Berlin")
)
- def order_change_allowed(self, order: Order, request: HttpRequest=None) -> bool:
+ def order_change_allowed(self, order: Order, request: HttpRequest = None) -> bool:
return super().order_change_allowed(order, request) and now() < datetime(
2024, 11, 29, 0, 0, 0, tzinfo=zoneinfo.ZoneInfo("Europe/Berlin")
)
def payment_form_render(self, request) -> str:
- template = get_template('pretixplugins/stripe/checkout_payment_form_simple.html')
+ template = get_template(
+ "pretixplugins/stripe/checkout_payment_form_simple.html"
+ )
ctx = {
- 'request': request,
- 'event': self.event,
- 'settings': self.settings,
- 'explanation': self.explanation,
- 'form': self.payment_form(request)
+ "request": request,
+ "event": self.event,
+ "settings": self.settings,
+ "explanation": self.explanation,
+ "form": self.payment_form(request),
}
return template.render(ctx)
@property
def payment_form_fields(self):
- return OrderedDict([
- ('bank_country', forms.ChoiceField(label=_('Country of your bank'), choices=(
- ('de', _('Germany')),
- ('at', _('Austria')),
- ('be', _('Belgium')),
- ('nl', _('Netherlands')),
- ('es', _('Spain'))
- ))),
- ])
+ return OrderedDict(
+ [
+ (
+ "bank_country",
+ forms.ChoiceField(
+ label=_("Country of your bank"),
+ choices=(
+ ("de", _("Germany")),
+ ("at", _("Austria")),
+ ("be", _("Belgium")),
+ ("nl", _("Netherlands")),
+ ("es", _("Spain")),
+ ),
+ ),
+ ),
+ ]
+ )
def _payment_intent_kwargs(self, request, payment):
return {
"payment_method_data": {
"type": "sofort",
"sofort": {
- "country": (request.session.get(f"payment_stripe_{self.method}_bank_country") or "DE").upper()
+ "country": (
+ request.session.get(
+ f"payment_stripe_{self.method}_bank_country"
+ )
+ or "DE"
+ ).upper()
},
}
}
@@ -1644,38 +2052,40 @@ class StripeSofort(StripeRedirectMethod):
try:
return super().execute_payment(request, payment)
finally:
- if f'payment_stripe_{self.method}_bank_country' in request.session:
- del request.session[f'payment_stripe_{self.method}_bank_country']
+ if f"payment_stripe_{self.method}_bank_country" in request.session:
+ del request.session[f"payment_stripe_{self.method}_bank_country"]
def payment_is_valid_session(self, request):
return (
- request.session.get(f'payment_stripe_{self.method}_bank_country', '') != ''
+ request.session.get(f"payment_stripe_{self.method}_bank_country", "") != ""
)
def checkout_prepare(self, request, cart):
form = self.payment_form(request)
if form.is_valid():
- request.session[f'payment_stripe_{self.method}_bank_country'] = form.cleaned_data['bank_country']
+ request.session[f"payment_stripe_{self.method}_bank_country"] = (
+ form.cleaned_data["bank_country"]
+ )
return True
return False
def payment_presale_render(self, payment: OrderPayment) -> str:
pi = payment.info_data or {}
try:
- return gettext('Bank account {iban} at {bank}').format(
+ 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"]
+ bank=pi["source"]["sofort"]["bank_name"],
)
except:
- logger.exception('Could not parse payment data')
+ logger.exception("Could not parse payment data")
return super().payment_presale_render(payment)
class StripeEPS(StripeRedirectWithAccountNamePaymentIntentMethod):
- identifier = 'stripe_eps'
- verbose_name = _('EPS via Stripe')
- public_name = _('EPS')
- method = 'eps'
+ identifier = "stripe_eps"
+ verbose_name = _("EPS via Stripe")
+ public_name = _("EPS")
+ method = "eps"
redirect_in_widget_allowed = False
def _payment_intent_kwargs(self, request, payment):
@@ -1683,7 +2093,8 @@ class StripeEPS(StripeRedirectWithAccountNamePaymentIntentMethod):
"payment_method_data": {
"type": "eps",
"billing_details": {
- "name": request.session.get(f"payment_stripe_{self.method}_account") or gettext("unknown name")
+ "name": request.session.get(f"payment_stripe_{self.method}_account")
+ or gettext("unknown name")
},
}
}
@@ -1691,24 +2102,29 @@ class StripeEPS(StripeRedirectWithAccountNamePaymentIntentMethod):
def payment_presale_render(self, payment: OrderPayment) -> str:
pi = payment.info_data or {}
try:
- return gettext('Bank account at {bank}').format(
+ return gettext("Bank account at {bank}").format(
bank=(
- pi.get("latest_charge", {}).get("payment_method_details", {}).get("eps", {}).get("bank") or
- pi.get("source", {}).get("eps", {}).get("bank", "?")
- ).replace("_", " ").title()
+ pi.get("latest_charge", {})
+ .get("payment_method_details", {})
+ .get("eps", {})
+ .get("bank")
+ or pi.get("source", {}).get("eps", {}).get("bank", "?")
+ )
+ .replace("_", " ")
+ .title()
)
except:
- logger.exception('Could not parse payment data')
+ logger.exception("Could not parse payment data")
return super().payment_presale_render(payment)
class StripeMultibanco(StripeRedirectMethod):
- identifier = 'stripe_multibanco'
- verbose_name = _('Multibanco via Stripe')
- public_name = _('Multibanco')
- method = 'multibanco'
+ identifier = "stripe_multibanco"
+ verbose_name = _("Multibanco via Stripe")
+ public_name = _("Multibanco")
+ method = "multibanco"
explanation = _(
- 'Multibanco is a payment method available to Portuguese bank account holders.'
+ "Multibanco is a payment method available to Portuguese bank account holders."
)
redirect_in_widget_allowed = False
abort_pending_allowed = True
@@ -1719,19 +2135,19 @@ class StripeMultibanco(StripeRedirectMethod):
"type": "multibanco",
"billing_details": {
"email": payment.order.email,
- }
+ },
}
}
class StripePrzelewy24(StripeRedirectMethod):
- identifier = 'stripe_przelewy24'
- verbose_name = _('Przelewy24 via Stripe')
- public_name = _('Przelewy24')
- method = 'p24'
+ identifier = "stripe_przelewy24"
+ verbose_name = _("Przelewy24 via Stripe")
+ public_name = _("Przelewy24")
+ method = "p24"
explanation = _(
- 'Przelewy24 is an online payment method available to customers of Polish banks. Please keep your online '
- 'banking account and login information available.'
+ "Przelewy24 is an online payment method available to customers of Polish banks. Please keep your online "
+ "banking account and login information available."
)
redirect_in_widget_allowed = False
@@ -1739,44 +2155,51 @@ class StripePrzelewy24(StripeRedirectMethod):
return {
"payment_method_data": {
"type": "p24",
- "billing_details": {
- "email": payment.order.email
- },
+ "billing_details": {"email": payment.order.email},
}
}
@property
def is_enabled(self) -> bool:
- return self.settings.get('_enabled', as_type=bool) and self.settings.get('method_przelewy24', as_type=bool)
+ return self.settings.get("_enabled", as_type=bool) and self.settings.get(
+ "method_przelewy24", as_type=bool
+ )
def payment_presale_render(self, payment: OrderPayment) -> str:
pi = payment.info_data or {}
try:
- return gettext('Bank account at {bank}').format(
+ return gettext("Bank account at {bank}").format(
bank=(
- pi.get("latest_charge", {}).get("payment_method_details", {}).get("p24", {}).get("bank") or
- pi.get("source", {}).get("p24", {}).get("bank", "?")
- ).replace("_", " ").title()
+ pi.get("latest_charge", {})
+ .get("payment_method_details", {})
+ .get("p24", {})
+ .get("bank")
+ or pi.get("source", {}).get("p24", {}).get("bank", "?")
+ )
+ .replace("_", " ")
+ .title()
)
except:
- logger.exception('Could not parse payment data')
+ logger.exception("Could not parse payment data")
return super().payment_presale_render(payment)
class StripeWeChatPay(StripeRedirectMethod):
- identifier = 'stripe_wechatpay'
- verbose_name = _('WeChat Pay via Stripe')
- public_name = _('WeChat Pay')
- method = 'wechat_pay'
- confirmation_method = 'automatic'
+ identifier = "stripe_wechatpay"
+ verbose_name = _("WeChat Pay via Stripe")
+ public_name = _("WeChat Pay")
+ method = "wechat_pay"
+ confirmation_method = "automatic"
explanation = _(
- 'This payment method is available to users of the Chinese app WeChat. Please keep your login information '
- 'available.'
+ "This payment method is available to users of the Chinese app WeChat. Please keep your login information "
+ "available."
)
@property
def is_enabled(self) -> bool:
- return self.settings.get('_enabled', as_type=bool) and self.settings.get('method_wechatpay', as_type=bool)
+ return self.settings.get("_enabled", as_type=bool) and self.settings.get(
+ "method_wechatpay", as_type=bool
+ )
def _payment_intent_kwargs(self, request, payment):
return {
@@ -1784,22 +2207,20 @@ class StripeWeChatPay(StripeRedirectMethod):
"type": "wechat_pay",
},
"payment_method_options": {
- "wechat_pay": {
- "client": "web"
- },
- }
+ "wechat_pay": {"client": "web"},
+ },
}
class StripeRevolutPay(StripeRedirectMethod):
- identifier = 'stripe_revolut_pay'
- verbose_name = _('Revolut Pay via Stripe')
- public_name = _('Revolut Pay')
- method = 'revolut_pay'
- confirmation_method = 'automatic'
+ identifier = "stripe_revolut_pay"
+ verbose_name = _("Revolut Pay via Stripe")
+ public_name = _("Revolut Pay")
+ method = "revolut_pay"
+ confirmation_method = "automatic"
explanation = _(
- 'This payment method is available to users of the Revolut app. Please keep your login information '
- 'available.'
+ "This payment method is available to users of the Revolut app. Please keep your login information "
+ "available."
)
def _payment_intent_kwargs(self, request, payment):
@@ -1811,22 +2232,22 @@ class StripeRevolutPay(StripeRedirectMethod):
class StripePayPal(StripeRedirectMethod):
- identifier = 'stripe_paypal'
- verbose_name = _('PayPal via Stripe')
- public_name = _('PayPal')
- method = 'paypal'
+ identifier = "stripe_paypal"
+ verbose_name = _("PayPal via Stripe")
+ public_name = _("PayPal")
+ method = "paypal"
redirect_in_widget_allowed = False
class StripeSwish(StripeRedirectMethod):
- identifier = 'stripe_swish'
- verbose_name = _('Swish via Stripe')
- public_name = _('Swish')
- method = 'swish'
- confirmation_method = 'automatic'
+ identifier = "stripe_swish"
+ verbose_name = _("Swish via Stripe")
+ public_name = _("Swish")
+ method = "swish"
+ confirmation_method = "automatic"
explanation = _(
- 'This payment method is available to users of the Swedish apps Swish and BankID. Please have your app '
- 'ready.'
+ "This payment method is available to users of the Swedish apps Swish and BankID. Please have your app "
+ "ready."
)
def _payment_intent_kwargs(self, request, payment):
@@ -1838,23 +2259,27 @@ class StripeSwish(StripeRedirectMethod):
"swish": {
"reference": payment.order.full_code,
},
- }
+ },
}
class StripeTwint(StripeRedirectMethod):
- identifier = 'stripe_twint'
- verbose_name = _('TWINT via Stripe')
- public_name = 'TWINT'
- method = 'twint'
- confirmation_method = 'automatic'
+ identifier = "stripe_twint"
+ verbose_name = _("TWINT via Stripe")
+ public_name = "TWINT"
+ method = "twint"
+ confirmation_method = "automatic"
explanation = _(
- 'This payment method is available to users of the Swiss app TWINT. Please have your app '
- 'ready.'
+ "This payment method is available to users of the Swiss app TWINT. Please have your app "
+ "ready."
)
- def is_allowed(self, request: HttpRequest, total: Decimal=None) -> bool:
- return super().is_allowed(request, total) and request.event.currency == "CHF" and total <= Decimal("5000.00")
+ def is_allowed(self, request: HttpRequest, total: Decimal = None) -> bool:
+ return (
+ super().is_allowed(request, total)
+ and request.event.currency == "CHF"
+ and total <= Decimal("5000.00")
+ )
def _payment_intent_kwargs(self, request, payment):
return {
@@ -1865,13 +2290,13 @@ class StripeTwint(StripeRedirectMethod):
class StripeMobilePay(StripeRedirectMethod):
- identifier = 'stripe_mobilepay'
- verbose_name = 'MobilePay via Stripe'
- public_name = 'MobilePay'
- method = 'mobilepay'
- confirmation_method = 'automatic'
+ identifier = "stripe_mobilepay"
+ verbose_name = "MobilePay via Stripe"
+ public_name = "MobilePay"
+ method = "mobilepay"
+ confirmation_method = "automatic"
explanation = _(
- 'This payment method is available to MobilePay app users in Denmark and Finland. Please have your app ready.'
+ "This payment method is available to MobilePay app users in Denmark and Finland. Please have your app ready."
)
def _payment_intent_kwargs(self, request, payment):
diff --git a/src/pretix/plugins/stripe/tasks.py b/src/pretix/plugins/stripe/tasks.py
index cbf04fd35..497c297bf 100644
--- a/src/pretix/plugins/stripe/tasks.py
+++ b/src/pretix/plugins/stripe/tasks.py
@@ -29,6 +29,7 @@ from pretix.base.services.tasks import EventTask
from pretix.celery_app import app
from pretix.multidomain.urlreverse import get_event_domain
from pretix.plugins.stripe.models import RegisteredApplePayDomain
+from pretix.plugins.stripe.utils import get_stripe_client
logger = logging.getLogger(__name__)
@@ -51,6 +52,7 @@ def get_stripe_account_key(prov):
@app.task(base=EventTask, max_retries=5, default_retry_delay=1)
def stripe_verify_domain(event, domain):
from pretix.plugins.stripe.payment import StripeCC
+
prov = StripeCC(event)
account = get_stripe_account_key(prov)
@@ -59,29 +61,22 @@ def stripe_verify_domain(event, domain):
# we're building our api_kwargs here by hand.
# Only if no live connect secret key is set, we'll fall back to the testmode keys.
# But this should never happen except in scenarios where pretix runs in devmode.
- if prov.settings.connect_client_id and prov.settings.connect_user_id:
- api_kwargs = {
- 'api_key': prov.settings.connect_secret_key or prov.settings.connect_test_secret_key,
- 'stripe_account': prov.settings.connect_user_id
- }
- else:
- api_kwargs = {
- 'api_key': prov.settings.secret_key,
- }
+ stripe_client = get_stripe_client(
+ prov.settings.connect_secret_key or prov.settings.connect_test_secret_key
+ )
if RegisteredApplePayDomain.objects.filter(account=account, domain=domain).exists():
return
try:
- resp = stripe.ApplePayDomain.create(
- domain_name=domain,
- **api_kwargs
+ resp = stripe_client.apple_pay_domains.create(
+ params={
+ "domain_name": domain,
+ },
+ options=prov.api_options,
)
except stripe.error.StripeError:
- logger.exception('Could not verify domain with Stripe')
+ logger.exception("Could not verify domain with Stripe")
else:
if resp.livemode:
- RegisteredApplePayDomain.objects.create(
- domain=domain,
- account=account
- )
+ RegisteredApplePayDomain.objects.create(domain=domain, account=account)
diff --git a/src/pretix/plugins/stripe/utils.py b/src/pretix/plugins/stripe/utils.py
index 9fd5bdc50..f0a3d7acd 100644
--- a/src/pretix/plugins/stripe/utils.py
+++ b/src/pretix/plugins/stripe/utils.py
@@ -19,3 +19,18 @@
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# .
#
+import stripe
+from stripe import StripeClient
+
+from pretix import __version__
+
+
+def get_stripe_client(api_key):
+ stripe.set_app_info(
+ "pretix",
+ partner_id="pp_partner_FSaz4PpKIur7Ox",
+ version=__version__,
+ url="https://pretix.eu",
+ )
+ stripe.enable_telemetry = False
+ return StripeClient(api_key)
diff --git a/src/pretix/plugins/stripe/views.py b/src/pretix/plugins/stripe/views.py
index 628323dac..3151024b0 100644
--- a/src/pretix/plugins/stripe/views.py
+++ b/src/pretix/plugins/stripe/views.py
@@ -59,7 +59,8 @@ from pretix.base.payment import PaymentException
from pretix.base.services.locking import LockTimeoutException
from pretix.base.settings import GlobalSettingsObject
from pretix.control.permissions import (
- AdministratorPermissionRequiredMixin, event_permission_required,
+ AdministratorPermissionRequiredMixin,
+ event_permission_required,
)
from pretix.control.views.event import DecoupleMixin
from pretix.control.views.organizer import OrganizerDetailViewMixin
@@ -70,181 +71,253 @@ from pretix.plugins.stripe.forms import OrganizerStripeSettingsForm
from pretix.plugins.stripe.models import ReferencedStripeObject
from pretix.plugins.stripe.payment import StripeCC, StripeSettingsHolder
from pretix.plugins.stripe.tasks import (
- get_domain_for_event, stripe_verify_domain,
+ get_domain_for_event,
+ stripe_verify_domain,
)
+from pretix.plugins.stripe.utils import get_stripe_client
-logger = logging.getLogger('pretix.plugins.stripe')
+logger = logging.getLogger("pretix.plugins.stripe")
@xframe_options_exempt
def redirect_view(request, *args, **kwargs):
try:
- data = signing.loads(request.GET.get('data', ''), salt='safe-redirect')
+ data = signing.loads(request.GET.get("data", ""), salt="safe-redirect")
except signing.BadSignature:
- return HttpResponseBadRequest('Invalid parameter')
+ return HttpResponseBadRequest("Invalid parameter")
- if 'go' in request.GET:
- if 'session' in data:
- for k, v in data['session'].items():
+ if "go" in request.GET:
+ if "session" in data:
+ for k, v in data["session"].items():
request.session[k] = v
- return redirect(data['url'])
+ return redirect(data["url"])
else:
params = request.GET.copy()
- params['go'] = '1'
- r = render(request, 'pretixplugins/stripe/redirect.html', {
- 'url': build_absolute_uri(request.event, 'plugins:stripe:redirect') + '?' + urllib.parse.urlencode(params),
- })
+ params["go"] = "1"
+ r = render(
+ request,
+ "pretixplugins/stripe/redirect.html",
+ {
+ "url": build_absolute_uri(request.event, "plugins:stripe:redirect")
+ + "?"
+ + urllib.parse.urlencode(params),
+ },
+ )
r._csp_ignore = True
return r
@scopes_disabled()
def oauth_return(request, *args, **kwargs):
- if 'payment_stripe_oauth_event' not in request.session:
- messages.error(request, _('An error occurred during connecting with Stripe, please try again.'))
- return redirect('control:index')
+ if "payment_stripe_oauth_event" not in request.session:
+ messages.error(
+ request,
+ _("An error occurred during connecting with Stripe, please try again."),
+ )
+ return redirect("control:index")
- event = get_object_or_404(Event, pk=request.session['payment_stripe_oauth_event'])
+ event = get_object_or_404(Event, pk=request.session["payment_stripe_oauth_event"])
- if request.GET.get('state') != request.session['payment_stripe_oauth_token']:
- messages.error(request, _('An error occurred during connecting with Stripe, please try again.'))
- return redirect_to_url(reverse('control:event.settings.payment.provider', kwargs={
- 'organizer': event.organizer.slug,
- 'event': event.slug,
- 'provider': 'stripe_settings'
- }))
+ if request.GET.get("state") != request.session["payment_stripe_oauth_token"]:
+ messages.error(
+ request,
+ _("An error occurred during connecting with Stripe, please try again."),
+ )
+ return redirect_to_url(
+ reverse(
+ "control:event.settings.payment.provider",
+ kwargs={
+ "organizer": event.organizer.slug,
+ "event": event.slug,
+ "provider": "stripe_settings",
+ },
+ )
+ )
gs = GlobalSettingsObject()
testdata = {}
+ stripe_client = get_stripe_client(
+ gs.settings.payment_stripe_connect_secret_key
+ or gs.settings.payment_stripe_connect_test_secret_key
+ )
try:
- resp = requests.post('https://connect.stripe.com/oauth/token', data={
- 'grant_type': 'authorization_code',
- 'client_secret': (
- gs.settings.payment_stripe_connect_secret_key or gs.settings.payment_stripe_connect_test_secret_key
- ),
- 'code': request.GET.get('code')
- })
+ resp = requests.post(
+ "https://connect.stripe.com/oauth/token",
+ data={
+ "grant_type": "authorization_code",
+ "client_secret": (
+ gs.settings.payment_stripe_connect_secret_key
+ or gs.settings.payment_stripe_connect_test_secret_key
+ ),
+ "code": request.GET.get("code"),
+ },
+ )
data = resp.json()
- if 'error' not in data:
- account = stripe.Account.retrieve(
- data['stripe_user_id'],
- api_key=gs.settings.payment_stripe_connect_secret_key or gs.settings.payment_stripe_connect_test_secret_key
+ if "error" not in data:
+ account = stripe_client.accounts.retrieve(
+ data["stripe_user_id"],
)
except:
- logger.exception('Failed to obtain OAuth token')
- messages.error(request, _('An error occurred during connecting with Stripe, please try again.'))
+ logger.exception("Failed to obtain OAuth token")
+ messages.error(
+ request,
+ _("An error occurred during connecting with Stripe, please try again."),
+ )
else:
- if 'error' not in data and data['livemode']:
+ if "error" not in data and data["livemode"]:
try:
- testresp = requests.post('https://connect.stripe.com/oauth/token', data={
- 'grant_type': 'refresh_token',
- 'client_secret': gs.settings.payment_stripe_connect_test_secret_key,
- 'refresh_token': data['refresh_token']
- })
+ testresp = requests.post(
+ "https://connect.stripe.com/oauth/token",
+ data={
+ "grant_type": "refresh_token",
+ "client_secret": gs.settings.payment_stripe_connect_test_secret_key,
+ "refresh_token": data["refresh_token"],
+ },
+ )
testdata = testresp.json()
except:
- logger.exception('Failed to obtain OAuth token')
- messages.error(request, _('An error occurred during connecting with Stripe, please try again.'))
- return redirect_to_url(reverse('control:event.settings.payment.provider', kwargs={
- 'organizer': event.organizer.slug,
- 'event': event.slug,
- 'provider': 'stripe_settings'
- }))
-
- if 'error' in data:
- messages.error(request, _('Stripe returned an error: {}').format(data['error_description']))
- elif data['livemode'] and 'error' in testdata:
- messages.error(request, _('Stripe returned an error: {}').format(testdata['error_description']))
- else:
- messages.success(request,
- _('Your Stripe account is now connected to pretix. You can change the settings in '
- 'detail below.'))
- event.settings.payment_stripe_publishable_key = data['stripe_publishable_key']
- # event.settings.payment_stripe_connect_access_token = data['access_token'] we don't need it, right?
- event.settings.payment_stripe_connect_refresh_token = data['refresh_token']
- event.settings.payment_stripe_connect_user_id = data['stripe_user_id']
- event.settings.payment_stripe_merchant_country = account.get('country')
- if (
- account.get('business_profile', {}).get('name')
- or account.get('settings', {}).get('dashboard', {}).get('display_name')
- or account.get('email')
- ):
- event.settings.payment_stripe_connect_user_name = (
- account.get('business_profile', {}).get('name')
- or account.get('settings', {}).get('dashboard', {}).get('display_name')
- or account.get('email')
+ logger.exception("Failed to obtain OAuth token")
+ messages.error(
+ request,
+ _(
+ "An error occurred during connecting with Stripe, please try again."
+ ),
+ )
+ return redirect_to_url(
+ reverse(
+ "control:event.settings.payment.provider",
+ kwargs={
+ "organizer": event.organizer.slug,
+ "event": event.slug,
+ "provider": "stripe_settings",
+ },
+ )
)
- if data['livemode']:
- event.settings.payment_stripe_publishable_test_key = testdata['stripe_publishable_key']
+ if "error" in data:
+ messages.error(
+ request,
+ _("Stripe returned an error: {}").format(data["error_description"]),
+ )
+ elif data["livemode"] and "error" in testdata:
+ messages.error(
+ request,
+ _("Stripe returned an error: {}").format(testdata["error_description"]),
+ )
+ else:
+ messages.success(
+ request,
+ _(
+ "Your Stripe account is now connected to pretix. You can change the settings in "
+ "detail below."
+ ),
+ )
+ event.settings.payment_stripe_publishable_key = data[
+ "stripe_publishable_key"
+ ]
+ # event.settings.payment_stripe_connect_access_token = data['access_token'] we don't need it, right?
+ event.settings.payment_stripe_connect_refresh_token = data["refresh_token"]
+ event.settings.payment_stripe_connect_user_id = data["stripe_user_id"]
+ event.settings.payment_stripe_merchant_country = account.get("country")
+ if (
+ account.get("business_profile", {}).get("name")
+ or account.get("settings", {}).get("dashboard", {}).get("display_name")
+ or account.get("email")
+ ):
+ event.settings.payment_stripe_connect_user_name = (
+ account.get("business_profile", {}).get("name")
+ or account.get("settings", {})
+ .get("dashboard", {})
+ .get("display_name")
+ or account.get("email")
+ )
+
+ if data["livemode"]:
+ event.settings.payment_stripe_publishable_test_key = testdata[
+ "stripe_publishable_key"
+ ]
else:
- event.settings.payment_stripe_publishable_test_key = event.settings.payment_stripe_publishable_key
+ event.settings.payment_stripe_publishable_test_key = (
+ event.settings.payment_stripe_publishable_key
+ )
- if request.session.get('payment_stripe_oauth_enable', False):
+ if request.session.get("payment_stripe_oauth_enable", False):
event.settings.payment_stripe__enabled = True
- del request.session['payment_stripe_oauth_enable']
+ del request.session["payment_stripe_oauth_enable"]
- stripe_verify_domain.apply_async(args=(event.pk, get_domain_for_event(event)))
+ stripe_verify_domain.apply_async(
+ args=(event.pk, get_domain_for_event(event))
+ )
- return redirect_to_url(reverse('control:event.settings.payment.provider', kwargs={
- 'organizer': event.organizer.slug,
- 'event': event.slug,
- 'provider': 'stripe_settings'
- }))
+ return redirect_to_url(
+ reverse(
+ "control:event.settings.payment.provider",
+ kwargs={
+ "organizer": event.organizer.slug,
+ "event": event.slug,
+ "provider": "stripe_settings",
+ },
+ )
+ )
@csrf_exempt
@require_POST
@scopes_disabled()
def webhook(request, *args, **kwargs):
- event_json = json.loads(request.body.decode('utf-8'))
+ event_json = json.loads(request.body.decode("utf-8"))
# We do not check for the event type as we are not interested in the event it self,
# we just use it as a trigger to look the charge up to be absolutely sure.
# Another reason for this is that stripe events are not authenticated, so they could
# come from anywhere.
- if event_json['data']['object']['object'] == "charge":
+ if event_json["data"]["object"]["object"] == "charge":
func = charge_webhook
- objid = event_json['data']['object']['id']
+ objid = event_json["data"]["object"]["id"]
lookup_ids = [
objid,
- (event_json['data']['object'].get('source') or {}).get('id')
+ (event_json["data"]["object"].get("source") or {}).get("id"),
]
- elif event_json['data']['object']['object'] == "dispute":
+ elif event_json["data"]["object"]["object"] == "dispute":
func = charge_webhook
- objid = event_json['data']['object']['charge']
+ objid = event_json["data"]["object"]["charge"]
lookup_ids = [objid]
- elif event_json['data']['object']['object'] == "source":
+ elif event_json["data"]["object"]["object"] == "source":
func = source_webhook
- objid = event_json['data']['object']['id']
+ objid = event_json["data"]["object"]["id"]
lookup_ids = [objid]
- elif event_json['data']['object']['object'] == "payment_intent":
+ elif event_json["data"]["object"]["object"] == "payment_intent":
func = paymentintent_webhook
- objid = event_json['data']['object']['id']
+ objid = event_json["data"]["object"]["id"]
lookup_ids = [objid]
else:
return HttpResponse("Not interested in this data type", status=200)
- rso = ReferencedStripeObject.objects.select_related('order', 'order__event').filter(
- reference__in=[lid for lid in lookup_ids if lid]
- ).first()
+ rso = (
+ ReferencedStripeObject.objects.select_related("order", "order__event")
+ .filter(reference__in=[lid for lid in lookup_ids if lid])
+ .first()
+ )
if rso:
return func(rso.order.event, event_json, objid, rso)
else:
- if event_json['data']['object']['object'] == "charge" and 'payment_intent' in event_json['data']['object']:
+ if (
+ event_json["data"]["object"]["object"] == "charge"
+ and "payment_intent" in event_json["data"]["object"]
+ ):
# If we receive a charge webhook *before* the payment intent webhook, we don't know the charge ID yet
# and can't match it -- but we know the payment intent ID!
try:
- rso = ReferencedStripeObject.objects.select_related('order', 'order__event').get(
- reference=event_json['data']['object']['payment_intent']
- )
+ rso = ReferencedStripeObject.objects.select_related(
+ "order", "order__event"
+ ).get(reference=event_json["data"]["object"]["payment_intent"])
return func(rso.order.event, event_json, objid, rso)
except ReferencedStripeObject.DoesNotExist:
return HttpResponse("Unable to detect event", status=200)
- elif hasattr(request, 'event') and func != paymentintent_webhook:
+ elif hasattr(request, "event") and func != paymentintent_webhook:
# This is a legacy integration from back when didn't have ReferencedStripeObject. This can't happen for
# payment intents or charges connected with payment intents since they didn't exist back then. Our best
# hope is to go for request.event and see if we can find the order ID.
@@ -256,14 +329,14 @@ def webhook(request, *args, **kwargs):
SOURCE_TYPES = {
- 'sofort': 'stripe_sofort',
- 'three_d_secure': 'stripe',
- 'card': 'stripe',
- 'sepa_debit': 'stripe_sepa_debit',
- 'giropay': 'stripe_giropay',
- 'ideal': 'stripe_ideal',
- 'alipay': 'stripe_alipay',
- 'bancontact': 'stripe_bancontact',
+ "sofort": "stripe_sofort",
+ "three_d_secure": "stripe",
+ "card": "stripe",
+ "sepa_debit": "stripe_sepa_debit",
+ "giropay": "stripe_giropay",
+ "ideal": "stripe_ideal",
+ "alipay": "stripe_alipay",
+ "bancontact": "stripe_bancontact",
}
@@ -272,21 +345,28 @@ def charge_webhook(event, event_json, charge_id, rso):
prov._init_api()
try:
- charge = stripe.Charge.retrieve(
+ charge = prov.stripe_client.charges.retrieve(
charge_id,
- expand=['dispute', 'refunds', 'payment_intent', 'payment_intent.latest_charge'],
- **prov.api_kwargs
+ params={
+ "expand": [
+ "dispute",
+ "refunds",
+ "payment_intent",
+ "payment_intent.latest_charge",
+ ],
+ },
+ options=prov.api_options,
)
except stripe.error.StripeError:
- logger.exception('Stripe error on webhook. Event data: %s' % str(event_json))
- return HttpResponse('Charge not found', status=500)
+ logger.exception("Stripe error on webhook. Event data: %s" % str(event_json))
+ return HttpResponse("Charge not found", status=500)
- metadata = charge['metadata']
- if 'event' not in metadata:
- return HttpResponse('Event not given in charge metadata', status=200)
+ metadata = charge["metadata"]
+ if "event" not in metadata:
+ return HttpResponse("Event not given in charge metadata", status=200)
- if int(metadata['event']) != event.pk:
- return HttpResponse('Not interested in this event', status=200)
+ if int(metadata["event"]) != event.pk:
+ return HttpResponse("Not interested in this event", status=200)
if rso and rso.payment:
order = rso.payment.order
@@ -296,25 +376,36 @@ def charge_webhook(event, event_json, charge_id, rso):
payment = None
else:
try:
- order = event.orders.get(id=metadata['order'])
+ order = event.orders.get(id=metadata["order"])
except Order.DoesNotExist:
- return HttpResponse('Order not found', status=200)
+ return HttpResponse("Order not found", status=200)
payment = None
with transaction.atomic():
if payment:
- payment = OrderPayment.objects.select_for_update(of=OF_SELF).get(pk=payment.pk)
+ payment = OrderPayment.objects.select_for_update(of=OF_SELF).get(
+ pk=payment.pk
+ )
else:
- payment = order.payments.filter(
- info__icontains=charge['id'],
- provider__startswith='stripe',
- amount=prov._amount_to_decimal(charge['amount']),
- ).select_for_update(of=OF_SELF).last()
+ payment = (
+ order.payments.filter(
+ info__icontains=charge["id"],
+ provider__startswith="stripe",
+ amount=prov._amount_to_decimal(charge["amount"]),
+ )
+ .select_for_update(of=OF_SELF)
+ .last()
+ )
if not payment:
payment = order.payments.create(
state=OrderPayment.PAYMENT_STATE_CREATED,
- provider=SOURCE_TYPES.get(charge['source'].get('type', charge['source'].get('object', 'card')), 'stripe'),
- amount=prov._amount_to_decimal(charge['amount']),
+ provider=SOURCE_TYPES.get(
+ charge["source"].get(
+ "type", charge["source"].get("object", "card")
+ ),
+ "stripe",
+ ),
+ amount=prov._amount_to_decimal(charge["amount"]),
info=str(charge),
)
@@ -322,40 +413,47 @@ def charge_webhook(event, event_json, charge_id, rso):
prov = payment.payment_provider
prov._init_api()
- order.log_action('pretix.plugins.stripe.event', data=event_json)
+ order.log_action("pretix.plugins.stripe.event", data=event_json)
- is_refund = charge['amount_refunded'] or charge['refunds']['total_count'] or charge['dispute']
+ is_refund = (
+ charge["amount_refunded"]
+ or charge["refunds"]["total_count"]
+ or charge["dispute"]
+ )
if is_refund:
- known_refunds = [r.info_data.get('id') for r in payment.refunds.all()]
- migrated_refund_amounts = [r.amount for r in payment.refunds.all() if not r.info_data.get('id')]
- for r in charge['refunds']['data']:
- a = prov._amount_to_decimal(r['amount'])
- if r['status'] in ('failed', 'canceled'):
+ known_refunds = [r.info_data.get("id") for r in payment.refunds.all()]
+ migrated_refund_amounts = [
+ r.amount for r in payment.refunds.all() if not r.info_data.get("id")
+ ]
+ for r in charge["refunds"]["data"]:
+ a = prov._amount_to_decimal(r["amount"])
+ if r["status"] in ("failed", "canceled"):
continue
if a in migrated_refund_amounts:
migrated_refund_amounts.remove(a)
continue
- if r['id'] not in known_refunds:
- payment.create_external_refund(
- amount=a,
- info=str(r)
- )
- if charge['dispute']:
- if charge['dispute']['status'] != 'won' and charge['dispute']['id'] not in known_refunds:
- a = prov._amount_to_decimal(charge['dispute']['amount'])
+ if r["id"] not in known_refunds:
+ payment.create_external_refund(amount=a, info=str(r))
+ if charge["dispute"]:
+ if (
+ charge["dispute"]["status"] != "won"
+ and charge["dispute"]["id"] not in known_refunds
+ ):
+ a = prov._amount_to_decimal(charge["dispute"]["amount"])
if a in migrated_refund_amounts:
migrated_refund_amounts.remove(a)
else:
payment.create_external_refund(
- amount=a,
- info=str(charge['dispute'])
+ amount=a, info=str(charge["dispute"])
)
- elif charge['status'] == 'succeeded' and payment.state in (OrderPayment.PAYMENT_STATE_PENDING,
- OrderPayment.PAYMENT_STATE_CREATED,
- OrderPayment.PAYMENT_STATE_CANCELED,
- OrderPayment.PAYMENT_STATE_FAILED):
+ elif charge["status"] == "succeeded" and payment.state in (
+ OrderPayment.PAYMENT_STATE_PENDING,
+ OrderPayment.PAYMENT_STATE_CREATED,
+ OrderPayment.PAYMENT_STATE_CANCELED,
+ OrderPayment.PAYMENT_STATE_FAILED,
+ ):
try:
if getattr(charge, "payment_intent", None):
payment.info = str(charge.payment_intent)
@@ -364,7 +462,10 @@ def charge_webhook(event, event_json, charge_id, rso):
return HttpResponse("Lock timeout, please try again.", status=503)
except Quota.QuotaExceededException:
pass
- elif charge['status'] == 'failed' and payment.state in (OrderPayment.PAYMENT_STATE_PENDING, OrderPayment.PAYMENT_STATE_CREATED):
+ elif charge["status"] == "failed" and payment.state in (
+ OrderPayment.PAYMENT_STATE_PENDING,
+ OrderPayment.PAYMENT_STATE_CREATED,
+ ):
payment.fail(info=str(charge))
return HttpResponse(status=200)
@@ -374,17 +475,17 @@ def source_webhook(event, event_json, source_id, rso):
prov = StripeCC(event)
prov._init_api()
try:
- src = stripe.Source.retrieve(source_id, **prov.api_kwargs)
+ src = prov.stripe_client.sources.retrieve(source_id, options=prov.api_options)
except stripe.error.StripeError:
- logger.exception('Stripe error on webhook. Event data: %s' % str(event_json))
- return HttpResponse('Charge not found', status=500)
+ logger.exception("Stripe error on webhook. Event data: %s" % str(event_json))
+ return HttpResponse("Charge not found", status=500)
- metadata = src['metadata']
- if 'event' not in metadata:
- return HttpResponse('Event not given in charge metadata', status=200)
+ metadata = src["metadata"]
+ if "event" not in metadata:
+ return HttpResponse("Event not given in charge metadata", status=200)
- if int(metadata['event']) != event.pk:
- return HttpResponse('Not interested in this event', status=200)
+ if int(metadata["event"]) != event.pk:
+ return HttpResponse("Not interested in this event", status=200)
with transaction.atomic():
if rso and rso.payment:
@@ -395,24 +496,38 @@ def source_webhook(event, event_json, source_id, rso):
payment = None
else:
try:
- order = event.orders.get(id=metadata['order'])
+ order = event.orders.get(id=metadata["order"])
except Order.DoesNotExist:
- return HttpResponse('Order not found', status=200)
+ return HttpResponse("Order not found", status=200)
payment = None
if payment:
- payment = OrderPayment.objects.select_for_update(of=OF_SELF).get(pk=payment.pk)
+ payment = OrderPayment.objects.select_for_update(of=OF_SELF).get(
+ pk=payment.pk
+ )
else:
- payment = order.payments.filter(
- info__icontains=src['id'],
- provider__startswith='stripe',
- amount=prov._amount_to_decimal(src['amount']) if src['amount'] is not None else order.total,
- ).select_for_update(of=OF_SELF).last()
+ payment = (
+ order.payments.filter(
+ info__icontains=src["id"],
+ provider__startswith="stripe",
+ amount=(
+ prov._amount_to_decimal(src["amount"])
+ if src["amount"] is not None
+ else order.total
+ ),
+ )
+ .select_for_update(of=OF_SELF)
+ .last()
+ )
if not payment:
payment = order.payments.create(
state=OrderPayment.PAYMENT_STATE_CREATED,
- provider=SOURCE_TYPES.get(src['type'], 'stripe'),
- amount=prov._amount_to_decimal(src['amount']) if src['amount'] is not None else order.total,
+ provider=SOURCE_TYPES.get(src["type"], "stripe"),
+ amount=(
+ prov._amount_to_decimal(src["amount"])
+ if src["amount"] is not None
+ else order.total
+ ),
info=str(src),
)
@@ -420,18 +535,24 @@ def source_webhook(event, event_json, source_id, rso):
prov = payment.payment_provider
prov._init_api()
- order.log_action('pretix.plugins.stripe.event', data=event_json)
- go = (event_json['type'] == 'source.chargeable' and
- payment.state in (OrderPayment.PAYMENT_STATE_PENDING, OrderPayment.PAYMENT_STATE_CREATED) and
- src.status == 'chargeable')
+ order.log_action("pretix.plugins.stripe.event", data=event_json)
+ go = (
+ event_json["type"] == "source.chargeable"
+ and payment.state
+ in (OrderPayment.PAYMENT_STATE_PENDING, OrderPayment.PAYMENT_STATE_CREATED)
+ and src.status == "chargeable"
+ )
if go:
try:
prov._charge_source(None, source_id, payment)
except PaymentException:
- logger.exception('Webhook error')
- elif src.status == 'failed':
+ logger.exception("Webhook error")
+ elif src.status == "failed":
payment.fail(info=str(src))
- elif src.status == 'canceled' and payment.state in (OrderPayment.PAYMENT_STATE_PENDING, OrderPayment.PAYMENT_STATE_CREATED):
+ elif src.status == "canceled" and payment.state in (
+ OrderPayment.PAYMENT_STATE_PENDING,
+ OrderPayment.PAYMENT_STATE_CREATED,
+ ):
payment.info = str(src)
payment.state = OrderPayment.PAYMENT_STATE_CANCELED
payment.save()
@@ -444,15 +565,21 @@ def paymentintent_webhook(event, event_json, paymentintent_id, rso):
prov._init_api()
try:
- paymentintent = stripe.PaymentIntent.retrieve(paymentintent_id, expand=["latest_charge"], **prov.api_kwargs)
+ paymentintent = prov.stripe_client.payment_intents.retrieve(
+ paymentintent_id,
+ params={
+ "expand": ["latest_charge"],
+ },
+ options=prov.api_options,
+ )
except stripe.error.StripeError:
- logger.exception('Stripe error on webhook. Event data: %s' % str(event_json))
- return HttpResponse('Charge not found', status=500)
+ logger.exception("Stripe error on webhook. Event data: %s" % str(event_json))
+ return HttpResponse("Charge not found", status=500)
if paymentintent.latest_charge:
ReferencedStripeObject.objects.get_or_create(
reference=paymentintent.latest_charge.id,
- defaults={'order': rso.payment.order, 'payment': rso.payment}
+ defaults={"order": rso.payment.order, "payment": rso.payment},
)
if event_json["type"] == "payment_intent.payment_failed":
@@ -461,10 +588,10 @@ def paymentintent_webhook(event, event_json, paymentintent_id, rso):
return HttpResponse(status=200)
-@event_permission_required('can_change_event_settings')
+@event_permission_required("can_change_event_settings")
def oauth_disconnect(request, **kwargs):
if request.method != "POST":
- return render(request, 'pretixplugins/stripe/oauth_disconnect.html', {})
+ return render(request, "pretixplugins/stripe/oauth_disconnect.html", {})
del request.event.settings.payment_stripe_publishable_key
del request.event.settings.payment_stripe_publishable_test_key
@@ -473,27 +600,34 @@ def oauth_disconnect(request, **kwargs):
del request.event.settings.payment_stripe_connect_user_id
del request.event.settings.payment_stripe_connect_user_name
request.event.settings.payment_stripe__enabled = False
- messages.success(request, _('Your Stripe account has been disconnected.'))
+ messages.success(request, _("Your Stripe account has been disconnected."))
- return redirect_to_url(reverse('control:event.settings.payment.provider', kwargs={
- 'organizer': request.event.organizer.slug,
- 'event': request.event.slug,
- 'provider': 'stripe_settings'
- }))
+ return redirect_to_url(
+ reverse(
+ "control:event.settings.payment.provider",
+ kwargs={
+ "organizer": request.event.organizer.slug,
+ "event": request.event.slug,
+ "provider": "stripe_settings",
+ },
+ )
+ )
class StripeOrderView:
def dispatch(self, request, *args, **kwargs):
try:
self.order = request.event.orders.get_with_secret_check(
- code=kwargs['order'], received_secret=kwargs['hash'].lower(), tag='plugins:stripe'
+ code=kwargs["order"],
+ received_secret=kwargs["hash"].lower(),
+ tag="plugins:stripe",
)
except Order.DoesNotExist:
- raise Http404('Unknown order')
+ raise Http404("Unknown order")
self.payment = get_object_or_404(
self.order.payments,
- pk=self.kwargs['payment'],
- provider__startswith='stripe'
+ pk=self.kwargs["payment"],
+ provider__startswith="stripe",
)
return super().dispatch(request, *args, **kwargs)
@@ -502,119 +636,181 @@ class StripeOrderView:
return self.request.event.get_payment_providers()[self.payment.provider]
def _redirect_to_order(self):
- if self.request.session.get('payment_stripe_order_secret') != self.order.secret and not self.payment.provider.startswith('stripe'):
- messages.error(self.request, _('Sorry, there was an error in the payment process. Please check the link '
- 'in your emails to continue.'))
- return redirect_to_url(eventreverse(self.request.event, 'presale:event.index'))
+ if self.request.session.get(
+ "payment_stripe_order_secret"
+ ) != self.order.secret and not self.payment.provider.startswith("stripe"):
+ messages.error(
+ self.request,
+ _(
+ "Sorry, there was an error in the payment process. Please check the link "
+ "in your emails to continue."
+ ),
+ )
+ return redirect_to_url(
+ eventreverse(self.request.event, "presale:event.index")
+ )
- return redirect_to_url(eventreverse(self.request.event, 'presale:event.order', kwargs={
- 'order': self.order.code,
- 'secret': self.order.secret
- }) + ('?paid=yes' if self.order.status == Order.STATUS_PAID else ''))
+ return redirect_to_url(
+ eventreverse(
+ self.request.event,
+ "presale:event.order",
+ kwargs={"order": self.order.code, "secret": self.order.secret},
+ )
+ + ("?paid=yes" if self.order.status == Order.STATUS_PAID else "")
+ )
-@method_decorator(xframe_options_exempt, 'dispatch')
+@method_decorator(xframe_options_exempt, "dispatch")
class ReturnView(StripeOrderView, View):
def get(self, request, *args, **kwargs):
prov = self.pprov
prov._init_api()
try:
- src = stripe.Source.retrieve(request.GET.get('source'), **prov.api_kwargs)
+ src = prov.stripe_client.sources.retrieve(
+ request.GET.get("source"), options=prov.api_options
+ )
except stripe.error.InvalidRequestError:
- logger.exception('Could not retrieve source')
- messages.error(self.request, _('Sorry, there was an error in the payment process. Please check the link '
- 'in your emails to continue.'))
- return redirect_to_url(eventreverse(self.request.event, 'presale:event.index'))
+ logger.exception("Could not retrieve source")
+ messages.error(
+ self.request,
+ _(
+ "Sorry, there was an error in the payment process. Please check the link "
+ "in your emails to continue."
+ ),
+ )
+ return redirect_to_url(
+ eventreverse(self.request.event, "presale:event.index")
+ )
- if src.client_secret != request.GET.get('client_secret'):
- messages.error(self.request, _('Sorry, there was an error in the payment process. Please check the link '
- 'in your emails to continue.'))
- return redirect_to_url(eventreverse(self.request.event, 'presale:event.index'))
+ if src.client_secret != request.GET.get("client_secret"):
+ messages.error(
+ self.request,
+ _(
+ "Sorry, there was an error in the payment process. Please check the link "
+ "in your emails to continue."
+ ),
+ )
+ return redirect_to_url(
+ eventreverse(self.request.event, "presale:event.index")
+ )
with transaction.atomic():
self.order.refresh_from_db()
- self.payment = OrderPayment.objects.select_for_update(of=OF_SELF).get(pk=self.payment.pk)
+ self.payment = OrderPayment.objects.select_for_update(of=OF_SELF).get(
+ pk=self.payment.pk
+ )
if self.payment.state == OrderPayment.PAYMENT_STATE_CONFIRMED:
- if 'payment_stripe_token' in request.session:
- del request.session['payment_stripe_token']
+ if "payment_stripe_token" in request.session:
+ del request.session["payment_stripe_token"]
return self._redirect_to_order()
- if src.status == 'chargeable':
+ if src.status == "chargeable":
try:
prov._charge_source(request, src.id, self.payment)
except PaymentException as e:
messages.error(request, str(e))
return self._redirect_to_order()
finally:
- if 'payment_stripe_token' in request.session:
- del request.session['payment_stripe_token']
- elif src.status == 'consumed':
+ if "payment_stripe_token" in request.session:
+ del request.session["payment_stripe_token"]
+ elif src.status == "consumed":
# Webhook was faster, wow! ;)
- if 'payment_stripe_token' in request.session:
- del request.session['payment_stripe_token']
+ if "payment_stripe_token" in request.session:
+ del request.session["payment_stripe_token"]
return self._redirect_to_order()
- elif src.status == 'pending':
+ elif src.status == "pending":
self.payment.state = OrderPayment.PAYMENT_STATE_PENDING
self.payment.info = str(src)
self.payment.save()
else: # failed or canceled
self.payment.fail(info=str(src))
- messages.error(self.request, _('We had trouble authorizing your card payment. Please try again and '
- 'get in touch with us if this problem persists.'))
+ messages.error(
+ self.request,
+ _(
+ "We had trouble authorizing your card payment. Please try again and "
+ "get in touch with us if this problem persists."
+ ),
+ )
return self._redirect_to_order()
-@method_decorator(xframe_options_exempt, 'dispatch')
+@method_decorator(xframe_options_exempt, "dispatch")
class ScaView(StripeOrderView, View):
def get(self, request, *args, **kwargs):
prov = self.pprov
prov._init_api()
- if self.payment.state in (OrderPayment.PAYMENT_STATE_CONFIRMED,
- OrderPayment.PAYMENT_STATE_CANCELED,
- OrderPayment.PAYMENT_STATE_FAILED):
+ if self.payment.state in (
+ OrderPayment.PAYMENT_STATE_CONFIRMED,
+ OrderPayment.PAYMENT_STATE_CANCELED,
+ OrderPayment.PAYMENT_STATE_FAILED,
+ ):
return self._redirect_to_order()
payment_info = json.loads(self.payment.info)
- if 'id' in payment_info:
+ if "id" in payment_info:
try:
- intent = stripe.PaymentIntent.retrieve(
- payment_info['id'],
- expand=["latest_charge"],
- **prov.api_kwargs
+ intent = prov.stripe_client.payment_intents.retrieve(
+ payment_info["id"],
+ params={
+ "expand": ["latest_charge"],
+ },
+ options=prov.api_options,
)
except stripe.error.InvalidRequestError:
- logger.exception('Could not retrieve payment intent')
- messages.error(self.request, _('Sorry, there was an error in the payment process.'))
+ logger.exception("Could not retrieve payment intent")
+ messages.error(
+ self.request, _("Sorry, there was an error in the payment process.")
+ )
return self._redirect_to_order()
else:
- messages.error(self.request, _('Sorry, there was an error in the payment process.'))
+ messages.error(
+ self.request, _("Sorry, there was an error in the payment process.")
+ )
return self._redirect_to_order()
- if intent.status == 'requires_action' and intent.next_action.type in [
- 'use_stripe_sdk', 'redirect_to_url', 'alipay_handle_redirect', 'wechat_pay_display_qr_code',
- 'swish_handle_redirect_or_display_qr_code', 'multibanco_display_details',
+ if intent.status == "requires_action" and intent.next_action.type in [
+ "use_stripe_sdk",
+ "redirect_to_url",
+ "alipay_handle_redirect",
+ "wechat_pay_display_qr_code",
+ "swish_handle_redirect_or_display_qr_code",
+ "multibanco_display_details",
]:
ctx = {
- 'order': self.order,
- 'stripe_settings': StripeSettingsHolder(self.order.event).settings,
+ "order": self.order,
+ "stripe_settings": StripeSettingsHolder(self.order.event).settings,
}
- ctx['payment_intent_action_type'] = intent.next_action.type
- if intent.next_action.type in ('use_stripe_sdk', 'alipay_handle_redirect', 'wechat_pay_display_qr_code'):
- ctx['payment_intent_client_secret'] = intent.client_secret
- elif intent.next_action.type == 'redirect_to_url':
- ctx['payment_intent_next_action_redirect_url'] = intent.next_action.redirect_to_url['url']
- ctx['payment_intent_redirect_action_handling'] = prov.redirect_action_handling
- elif intent.next_action.type == 'swish_handle_redirect_or_display_qr_code':
- ctx['payment_intent_next_action_redirect_url'] = intent.next_action.swish_handle_redirect_or_display_qr_code['hosted_instructions_url']
- ctx['payment_intent_redirect_action_handling'] = 'iframe'
- elif intent.next_action.type == 'multibanco_display_details':
- ctx['payment_intent_next_action_redirect_url'] = intent.next_action.multibanco_display_details['hosted_voucher_url']
- ctx['payment_intent_redirect_action_handling'] = 'iframe'
+ ctx["payment_intent_action_type"] = intent.next_action.type
+ if intent.next_action.type in (
+ "use_stripe_sdk",
+ "alipay_handle_redirect",
+ "wechat_pay_display_qr_code",
+ ):
+ ctx["payment_intent_client_secret"] = intent.client_secret
+ elif intent.next_action.type == "redirect_to_url":
+ ctx["payment_intent_next_action_redirect_url"] = (
+ intent.next_action.redirect_to_url["url"]
+ )
+ ctx["payment_intent_redirect_action_handling"] = (
+ prov.redirect_action_handling
+ )
+ elif intent.next_action.type == "swish_handle_redirect_or_display_qr_code":
+ ctx["payment_intent_next_action_redirect_url"] = (
+ intent.next_action.swish_handle_redirect_or_display_qr_code[
+ "hosted_instructions_url"
+ ]
+ )
+ ctx["payment_intent_redirect_action_handling"] = "iframe"
+ elif intent.next_action.type == "multibanco_display_details":
+ ctx["payment_intent_next_action_redirect_url"] = (
+ intent.next_action.multibanco_display_details["hosted_voucher_url"]
+ )
+ ctx["payment_intent_redirect_action_handling"] = "iframe"
- r = render(request, 'pretixplugins/stripe/sca.html', ctx)
+ r = render(request, "pretixplugins/stripe/sca.html", ctx)
r._csp_ignore = True
return r
else:
@@ -626,7 +822,7 @@ class ScaView(StripeOrderView, View):
return self._redirect_to_order()
-@method_decorator(xframe_options_exempt, 'dispatch')
+@method_decorator(xframe_options_exempt, "dispatch")
class ScaReturnView(StripeOrderView, View):
def get(self, request, *args, **kwargs):
prov = self.pprov
@@ -638,31 +834,40 @@ class ScaReturnView(StripeOrderView, View):
self.order.refresh_from_db()
ctx = {
- 'order': self.order,
- 'payment_intent_redirect_action_handling': prov.redirect_action_handling,
- 'order_url': eventreverse(self.request.event, 'presale:event.order', kwargs={
- 'order': self.order.code,
- 'secret': self.order.secret
- }),
+ "order": self.order,
+ "payment_intent_redirect_action_handling": prov.redirect_action_handling,
+ "order_url": eventreverse(
+ self.request.event,
+ "presale:event.order",
+ kwargs={"order": self.order.code, "secret": self.order.secret},
+ ),
}
- return render(request, 'pretixplugins/stripe/sca_return.html', ctx)
+ return render(request, "pretixplugins/stripe/sca_return.html", ctx)
-class OrganizerSettingsFormView(DecoupleMixin, OrganizerDetailViewMixin, AdministratorPermissionRequiredMixin, FormView):
+class OrganizerSettingsFormView(
+ DecoupleMixin,
+ OrganizerDetailViewMixin,
+ AdministratorPermissionRequiredMixin,
+ FormView,
+):
model = Organizer
- permission = 'can_change_organizer_settings'
+ permission = "can_change_organizer_settings"
form_class = OrganizerStripeSettingsForm
- template_name = 'pretixplugins/stripe/organizer_stripe.html'
+ template_name = "pretixplugins/stripe/organizer_stripe.html"
def get_success_url(self):
- return reverse('plugins:stripe:settings.connect', kwargs={
- 'organizer': self.request.organizer.slug,
- })
+ return reverse(
+ "plugins:stripe:settings.connect",
+ kwargs={
+ "organizer": self.request.organizer.slug,
+ },
+ )
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
- kwargs['obj'] = self.request.organizer
+ kwargs["obj"] = self.request.organizer
return kwargs
@transaction.atomic
@@ -672,12 +877,15 @@ class OrganizerSettingsFormView(DecoupleMixin, OrganizerDetailViewMixin, Adminis
form.save()
if form.has_changed():
self.request.organizer.log_action(
- 'pretix.organizer.settings', user=self.request.user, data={
- k: form.cleaned_data.get(k) for k in form.changed_data
- }
+ "pretix.organizer.settings",
+ user=self.request.user,
+ data={k: form.cleaned_data.get(k) for k in form.changed_data},
)
- messages.success(self.request, _('Your changes have been saved.'))
+ messages.success(self.request, _("Your changes have been saved."))
return redirect_to_url(self.get_success_url())
else:
- messages.error(self.request, _('We could not save your changes. See below for details.'))
+ messages.error(
+ self.request,
+ _("We could not save your changes. See below for details."),
+ )
return self.get(request)