mirror of
https://github.com/pretix/pretix.git
synced 2026-05-04 15:04:03 +00:00
Stripe: Add Klarna (#3740)
* Stripe: Add Klarna * Improve country detection * Allow to select method * Fix isort --------- Co-authored-by: Raphael Michel <michel@rami.io>
This commit is contained in:
@@ -59,7 +59,9 @@ from django_countries import countries
|
||||
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
|
||||
from pretix.base.forms.questions import (
|
||||
guess_country, guess_country_from_request,
|
||||
)
|
||||
from pretix.base.models import (
|
||||
Event, InvoiceAddress, Order, OrderPayment, OrderRefund, Quota,
|
||||
)
|
||||
@@ -85,10 +87,11 @@ from pretix.presale.views.cart import cart_session
|
||||
|
||||
logger = logging.getLogger('pretix.plugins.stripe')
|
||||
|
||||
|
||||
# State of the payment methods
|
||||
#
|
||||
# Source: https://stripe.com/docs/payments/payment-methods/overview
|
||||
# Last Update: 2023-04-24
|
||||
# Last Update: 2023-12-20
|
||||
#
|
||||
# Cards
|
||||
# - Credit and Debit Cards: ✓
|
||||
@@ -125,9 +128,11 @@ logger = logging.getLogger('pretix.plugins.stripe')
|
||||
# Buy now, pay later
|
||||
# - Affirm: ✓
|
||||
# - Afterpay/Clearpay: ✗
|
||||
# - Klarna: ✗
|
||||
# - Klarna: ✓
|
||||
# - Zip: ✗
|
||||
#
|
||||
# Real-time payments
|
||||
# - Swish: ✗
|
||||
# - PayNow: ✗
|
||||
# - PromptPay: ✗
|
||||
# - Pix: ✗
|
||||
@@ -143,6 +148,7 @@ logger = logging.getLogger('pretix.plugins.stripe')
|
||||
# - Secure Remote Commerce: ✗
|
||||
# - Link: ✓ (PaymentRequestButton)
|
||||
# - Cash App Pay: ✗
|
||||
# - PayPal: ✗
|
||||
# - MobilePay: ✗
|
||||
# - Alipay: ✓
|
||||
# - WeChat Pay: ✓
|
||||
@@ -330,7 +336,7 @@ class StripeSettingsHolder(BasePaymentProvider):
|
||||
label=_('giropay'),
|
||||
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.'),
|
||||
'before they work properly.'),
|
||||
required=False,
|
||||
)),
|
||||
('method_ideal',
|
||||
@@ -338,7 +344,7 @@ class StripeSettingsHolder(BasePaymentProvider):
|
||||
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 work properly.'),
|
||||
'before they work properly.'),
|
||||
required=False,
|
||||
)),
|
||||
('method_alipay',
|
||||
@@ -346,7 +352,7 @@ class StripeSettingsHolder(BasePaymentProvider):
|
||||
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 work properly.'),
|
||||
'before they work properly.'),
|
||||
required=False,
|
||||
)),
|
||||
('method_bancontact',
|
||||
@@ -354,7 +360,7 @@ class StripeSettingsHolder(BasePaymentProvider):
|
||||
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 work properly.'),
|
||||
'before they work properly.'),
|
||||
required=False,
|
||||
)),
|
||||
('method_sepa_debit',
|
||||
@@ -404,7 +410,7 @@ class StripeSettingsHolder(BasePaymentProvider):
|
||||
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 work properly.'),
|
||||
'before they work properly.'),
|
||||
required=False,
|
||||
)),
|
||||
('method_multibanco',
|
||||
@@ -412,7 +418,7 @@ class StripeSettingsHolder(BasePaymentProvider):
|
||||
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 work properly.'),
|
||||
'before they work properly.'),
|
||||
required=False,
|
||||
)),
|
||||
('method_przelewy24',
|
||||
@@ -420,7 +426,7 @@ class StripeSettingsHolder(BasePaymentProvider):
|
||||
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 work properly.'),
|
||||
'before they work properly.'),
|
||||
required=False,
|
||||
)),
|
||||
('method_wechatpay',
|
||||
@@ -428,7 +434,7 @@ class StripeSettingsHolder(BasePaymentProvider):
|
||||
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 work properly.'),
|
||||
'before they work properly.'),
|
||||
required=False,
|
||||
)),
|
||||
('method_affirm',
|
||||
@@ -436,11 +442,28 @@ class StripeSettingsHolder(BasePaymentProvider):
|
||||
label=_('Affirm'),
|
||||
disabled=self.event.currency not in ['USD', 'CAD'],
|
||||
help_text=' '.join([
|
||||
str(_('Needs to be enabled in your Stripe account first.')),
|
||||
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,
|
||||
)),
|
||||
] + extra_fields + list(super().settings_form_fields.items()) + moto_settings
|
||||
)
|
||||
if not self.settings.connect_client_id or self.settings.secret_key:
|
||||
@@ -1336,15 +1359,112 @@ class StripeAffirm(StripePaymentIntentMethod):
|
||||
}
|
||||
|
||||
def payment_form_render(self, request, total, order=None) -> str:
|
||||
template = get_template('pretixplugins/stripe/checkout_payment_form_affirm.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),
|
||||
'method': self.method,
|
||||
}
|
||||
return template.render(ctx)
|
||||
|
||||
|
||||
class StripeKlarna(StripePaymentIntentMethod):
|
||||
identifier = "stripe_klarna"
|
||||
verbose_name = _("Klarna via Stripe")
|
||||
public_name = _("Klarna")
|
||||
method = "klarna"
|
||||
redirect_action_handling = "redirect"
|
||||
allowed_countries = {"US", "CA", "AU", "NZ", "GB", "IE", "FR", "ES", "DE", "AT", "BE", "DK", "FI", "IT", "NL", "NO", "SE"}
|
||||
|
||||
def payment_is_valid_session(self, request):
|
||||
# Klarna 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:
|
||||
return True
|
||||
return False
|
||||
|
||||
def checkout_prepare(self, request, cart):
|
||||
# Klarna 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
|
||||
return True
|
||||
|
||||
def _detect_country(self, request, order=None):
|
||||
def get_invoice_address():
|
||||
if order and getattr(order, 'invoice_address', None):
|
||||
request._checkout_flow_invoice_address = order.invoice_address
|
||||
if not hasattr(request, '_checkout_flow_invoice_address'):
|
||||
cs = cart_session(request)
|
||||
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)
|
||||
except InvoiceAddress.DoesNotExist:
|
||||
request._checkout_flow_invoice_address = InvoiceAddress()
|
||||
return request._checkout_flow_invoice_address
|
||||
|
||||
ia = get_invoice_address()
|
||||
country = None
|
||||
if ia.country:
|
||||
country = str(ia.country)
|
||||
if country not in self.allowed_countries:
|
||||
country = guess_country_from_request(request, self.event)
|
||||
if country not in self.allowed_countries:
|
||||
country = self.settings.merchant_country
|
||||
if country not in self.allowed_countries:
|
||||
country = "DE"
|
||||
return country
|
||||
|
||||
def _payment_intent_kwargs(self, request, payment):
|
||||
return {
|
||||
"payment_method_data": {
|
||||
"type": "klarna",
|
||||
"billing_details": {
|
||||
"email": payment.order.email,
|
||||
"address": {
|
||||
"country": self._detect_country(request, payment.order),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
def payment_form_render(self, request, total, order=None) -> str:
|
||||
template = get_template(
|
||||
"pretixplugins/stripe/checkout_payment_form_simple_messaging_noform.html"
|
||||
)
|
||||
ctx = {
|
||||
"request": request,
|
||||
"event": self.event,
|
||||
"total": self._decimal_to_int(total),
|
||||
"method": self.method,
|
||||
"country": self._detect_country(request, order)
|
||||
}
|
||||
return template.render(ctx)
|
||||
|
||||
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
|
||||
)
|
||||
if is_testmode:
|
||||
return mark_safe(
|
||||
_(
|
||||
"The Stripe plugin is operating in test mode. You can use one of <a {args}>many test "
|
||||
"cards</a> to perform a transaction. No money will actually be transferred."
|
||||
).format(
|
||||
args='href="https://docs.klarna.com/resources/test-environment/sample-customer-data/" target="_blank"'
|
||||
)
|
||||
)
|
||||
return None
|
||||
|
||||
|
||||
class StripeGiropay(StripeMethod):
|
||||
identifier = 'stripe_giropay'
|
||||
verbose_name = _('giropay via Stripe')
|
||||
|
||||
@@ -46,15 +46,15 @@ from pretix.presale.signals import html_head, process_response
|
||||
def register_payment_provider(sender, **kwargs):
|
||||
from .payment import (
|
||||
StripeAffirm, StripeAlipay, StripeBancontact, StripeCC, StripeEPS,
|
||||
StripeGiropay, StripeIdeal, StripeMultibanco, StripePrzelewy24,
|
||||
StripeSEPADirectDebit, StripeSettingsHolder, StripeSofort,
|
||||
StripeWeChatPay,
|
||||
StripeGiropay, StripeIdeal, StripeKlarna, StripeMultibanco,
|
||||
StripePrzelewy24, StripeSEPADirectDebit, StripeSettingsHolder,
|
||||
StripeSofort, StripeWeChatPay,
|
||||
)
|
||||
|
||||
return [
|
||||
StripeSettingsHolder, StripeCC, StripeGiropay, StripeIdeal, StripeAlipay, StripeBancontact,
|
||||
StripeSofort, StripeEPS, StripeMultibanco, StripePrzelewy24, StripeWeChatPay,
|
||||
StripeSEPADirectDebit, StripeAffirm,
|
||||
StripeSEPADirectDebit, StripeAffirm, StripeKlarna,
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ var pretixstripe = {
|
||||
card: null,
|
||||
sepa: null,
|
||||
affirm: null,
|
||||
klarna: null,
|
||||
paymentRequest: null,
|
||||
paymentRequestButton: null,
|
||||
|
||||
@@ -172,6 +173,21 @@ var pretixstripe = {
|
||||
|
||||
pretixstripe.affirm.mount('#stripe-affirm');
|
||||
}
|
||||
if ($("#stripe-klarna").length) {
|
||||
try {
|
||||
pretixstripe.klarna = pretixstripe.elements.create('paymentMethodMessaging', {
|
||||
'amount': parseInt($("#stripe_klarna_total").val()),
|
||||
'currency': $("#stripe_klarna_currency").val(),
|
||||
'countryCode': $("#stripe_klarna_country").val(),
|
||||
'paymentMethodTypes': ['klarna'],
|
||||
});
|
||||
|
||||
pretixstripe.klarna.mount('#stripe-klarna');
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
$("#stripe-klarna").html("<div class='alert alert-danger'>Technical error, please contact support: " + e + "</div>");
|
||||
}
|
||||
}
|
||||
if ($("#stripe-payment-request-button").length && pretixstripe.paymentRequest != null) {
|
||||
pretixstripe.paymentRequestButton = pretixstripe.elements.create('paymentRequestButton', {
|
||||
paymentRequest: pretixstripe.paymentRequest,
|
||||
@@ -280,11 +296,12 @@ $(function () {
|
||||
$("input[name=payment][value=stripe]").is(':checked')
|
||||
|| $("input[name=payment][value=stripe_sepa_debit]").is(':checked')
|
||||
|| $("input[name=payment][value=stripe_affirm]").is(':checked')
|
||||
|| $("input[name=payment][value=stripe_klarna]").is(':checked')
|
||||
|| $(".payment-redo-form").length) {
|
||||
pretixstripe.load();
|
||||
} else {
|
||||
$("input[name=payment]").change(function () {
|
||||
if (['stripe', 'stripe_sepa_debit', 'stripe_affirm'].indexOf($(this).val()) > -1) {
|
||||
if (['stripe', 'stripe_sepa_debit', 'stripe_affirm', 'stripe_klarna'].indexOf($(this).val()) > -1) {
|
||||
pretixstripe.load();
|
||||
}
|
||||
})
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
{% load bootstrap3 %}
|
||||
|
||||
<div class="form-horizontal stripe-container">
|
||||
<div id="stripe-affirm">
|
||||
<div id="stripe-{{ method }}">
|
||||
<span class="fa fa-spinner fa-spin"></span>
|
||||
<!-- a Stripe Element will be inserted here. -->
|
||||
</div>
|
||||
@@ -15,6 +15,9 @@
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
|
||||
<input type="hidden" name="stripe_affirm_total" value="{{ total }}" id="stripe_affirm_total"/>
|
||||
<input type="hidden" id="stripe_affirm_currency" value="{{ event.currency }}"/>
|
||||
<input type="hidden" name="stripe_{{ method }}_total" value="{{ total }}" id="stripe_{{ method }}_total"/>
|
||||
<input type="hidden" id="stripe_{{ method }}_currency" value="{{ event.currency }}"/>
|
||||
{% if country %}
|
||||
<input type="hidden" id="stripe_{{ method }}_country" value="{{ country }}"/>
|
||||
{% endif %}
|
||||
</div>
|
||||
Reference in New Issue
Block a user