Skip to content

Commit

Permalink
Stripe: Add Klarna (#3740)
Browse files Browse the repository at this point in the history
* Stripe: Add Klarna

* Improve country detection

* Allow to select method

* Fix isort

---------

Co-authored-by: Raphael Michel <michel@rami.io>
  • Loading branch information
pc-coholic and raphaelm committed Dec 20, 2023
1 parent 608d82c commit 8d9543c
Show file tree
Hide file tree
Showing 4 changed files with 161 additions and 21 deletions.
146 changes: 133 additions & 13 deletions src/pretix/plugins/stripe/payment.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,9 @@
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,
)
Expand All @@ -85,10 +87,11 @@

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: ✓
Expand Down Expand Up @@ -125,9 +128,11 @@
# Buy now, pay later
# - Affirm: ✓
# - Afterpay/Clearpay: ✗
# - Klarna: ✗
# - Klarna: ✓
# - Zip: ✗
#
# Real-time payments
# - Swish: ✗
# - PayNow: ✗
# - PromptPay: ✗
# - Pix: ✗
Expand All @@ -143,6 +148,7 @@
# - Secure Remote Commerce: ✗
# - Link: ✓ (PaymentRequestButton)
# - Cash App Pay: ✗
# - PayPal: ✗
# - MobilePay: ✗
# - Alipay: ✓
# - WeChat Pay: ✓
Expand Down Expand Up @@ -330,31 +336,31 @@ def settings_form_fields(self):
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',
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 work properly.'),
'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 work properly.'),
'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 work properly.'),
'before they work properly.'),
required=False,
)),
('method_sepa_debit',
Expand Down Expand Up @@ -404,43 +410,60 @@ def settings_form_fields(self):
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',
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 work properly.'),
'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 work properly.'),
'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 work properly.'),
'before they work properly.'),
required=False,
)),
('method_affirm',
forms.BooleanField(
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:
Expand Down Expand Up @@ -1336,14 +1359,111 @@ def _payment_intent_kwargs(self, request, payment):
}

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'
Expand Down
8 changes: 4 additions & 4 deletions src/pretix/plugins/stripe/signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,15 @@
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,
]


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ var pretixstripe = {
card: null,
sepa: null,
affirm: null,
klarna: null,
paymentRequest: null,
paymentRequestButton: null,

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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();
}
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand All @@ -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>

0 comments on commit 8d9543c

Please sign in to comment.