Compare commits

...

5 Commits

Author SHA1 Message Date
Martin Gross
451ed221d9 Settings: Add option to disable walletdetection 2023-07-11 11:05:09 +02:00
Richard Schreiber
b9af34f0fd convert walletdetection to promise with loading indicator 2023-07-05 14:18:55 +02:00
Martin Gross
fc15811b7f isort 2023-06-30 11:32:11 +02:00
Martin Gross
fdb2a20514 Add documentation 2023-06-30 10:47:28 +02:00
Martin Gross
1b1c4358d3 PProv: Implement detection of wallets such as Google Pay and Apple Pay 2023-06-30 10:21:22 +02:00
9 changed files with 145 additions and 4 deletions

View File

@@ -70,6 +70,8 @@ The provider class
.. autoattribute:: settings_form_fields .. autoattribute:: settings_form_fields
.. autoattribute:: walletqueries
.. automethod:: settings_form_clean .. automethod:: settings_form_clean
.. automethod:: settings_content_render .. automethod:: settings_content_render

View File

@@ -249,7 +249,7 @@ class SecurityMiddleware(MiddlewareMixin):
h = { h = {
'default-src': ["{static}"], 'default-src': ["{static}"],
'script-src': ['{static}', 'https://checkout.stripe.com', 'https://js.stripe.com'], 'script-src': ['{static}', 'https://checkout.stripe.com', 'https://js.stripe.com', 'https://pay.google.com'],
'object-src': ["'none'"], 'object-src': ["'none'"],
'frame-src': ['{static}', 'https://checkout.stripe.com', 'https://js.stripe.com'], 'frame-src': ['{static}', 'https://checkout.stripe.com', 'https://js.stripe.com'],
'style-src': ["{static}", "{media}"], 'style-src': ["{static}", "{media}"],

View File

@@ -78,6 +78,16 @@ from pretix.presale.views.cart import cart_session, get_or_create_cart_id
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class WalletQueries:
APPLEPAY = 'applepay'
GOOGLEPAY = 'googlepay'
WALLETS = (
(APPLEPAY, pgettext_lazy('payment', 'Apple Pay')),
(GOOGLEPAY, pgettext_lazy('payment', 'Google Pay')),
)
class PaymentProviderForm(Form): class PaymentProviderForm(Form):
def clean(self): def clean(self):
cleaned_data = super().clean() cleaned_data = super().clean()
@@ -436,6 +446,19 @@ class BasePaymentProvider:
d['_restrict_to_sales_channels']._as_type = list d['_restrict_to_sales_channels']._as_type = list
return d return d
@property
def walletqueries(self):
"""
.. warning:: This property is considered **experimental**. It might change or get removed at any time without
prior notice.
A list of wallet payment methods that should be dynamically joined to the public name of the payment method,
if they are available to the user.
The detection is made on a best effort basis with no guarantees of correctness and actual availability.
Wallets that pretix can check for are exposed through ``pretix.base.payment.WalletQueries``.
"""
return []
def settings_form_clean(self, cleaned_data): def settings_form_clean(self, cleaned_data):
""" """
Overriding this method allows you to inject custom validation into the settings form. Overriding this method allows you to inject custom validation into the settings form.

View File

@@ -59,7 +59,9 @@ from pretix import __version__
from pretix.base.decimal import round_decimal from pretix.base.decimal import round_decimal
from pretix.base.forms import SecretKeySettingsField from pretix.base.forms import SecretKeySettingsField
from pretix.base.models import Event, OrderPayment, OrderRefund, Quota from pretix.base.models import Event, OrderPayment, OrderRefund, Quota
from pretix.base.payment import BasePaymentProvider, PaymentException from pretix.base.payment import (
BasePaymentProvider, PaymentException, WalletQueries,
)
from pretix.base.plugins import get_all_plugins from pretix.base.plugins import get_all_plugins
from pretix.base.services.mail import SendMailException from pretix.base.services.mail import SendMailException
from pretix.base.settings import SettingsSandbox from pretix.base.settings import SettingsSandbox
@@ -219,6 +221,20 @@ class StripeSettingsHolder(BasePaymentProvider):
] ]
extra_fields = [ extra_fields = [
('walletdetection',
forms.BooleanField(
label=mark_safe(
_('Check for Apple Pay/Google Pay') +
' ' +
'<span class="label label-info">{}</span>'.format(_('experimental'))
),
help_text=_("pretix will attempt to check if the customer's webbrowser 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', ('postfix',
forms.CharField( forms.CharField(
label=_('Statement descriptor postfix'), label=_('Statement descriptor postfix'),
@@ -747,6 +763,15 @@ class StripeCC(StripeMethod):
public_name = _('Credit card') public_name = _('Credit card')
method = 'cc' method = 'cc'
@property
def walletqueries(self):
# ToDo: Check against Stripe API, if ApplePay and GooglePay are even activated/available
# This is probably only really feasable once the Payment Methods Configuration API is out of beta
# https://stripe.com/docs/connect/payment-method-configurations
if self.settings.get("walletdetection", True, as_type=bool):
return [WalletQueries.APPLEPAY, WalletQueries.GOOGLEPAY]
return []
def payment_form_render(self, request, total) -> str: def payment_form_render(self, request, total) -> str:
account = get_stripe_account_key(self) 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():

View File

@@ -3,6 +3,10 @@
{% load money %} {% load money %}
{% load bootstrap3 %} {% load bootstrap3 %}
{% load rich_text %} {% load rich_text %}
{% block custom_header %}
{{ block.super }}
{% include "pretixpresale/event/fragment_walletdetection_head.html" %}
{% endblock %}
{% block inner %} {% block inner %}
{% if current_payments %} {% if current_payments %}
<p>{% trans "You already selected the following payment methods:" %}</p> <p>{% trans "You already selected the following payment methods:" %}</p>
@@ -71,7 +75,8 @@
{% if selected == p.provider.identifier %}checked="checked"{% endif %} {% if selected == p.provider.identifier %}checked="checked"{% endif %}
id="input_payment_{{ p.provider.identifier }}" id="input_payment_{{ p.provider.identifier }}"
aria-describedby="payment_{{ p.provider.identifier }}" aria-describedby="payment_{{ p.provider.identifier }}"
data-toggle="radiocollapse" data-target="#payment_{{ p.provider.identifier }}"/> data-toggle="radiocollapse" data-target="#payment_{{ p.provider.identifier }}"
data-wallets="{{ p.provider.walletqueries|join:"|" }}" />
<label for="input_payment_{{ p.provider.identifier }}"><strong>{{ p.provider.public_name }}</strong></label> <label for="input_payment_{{ p.provider.identifier }}"><strong>{{ p.provider.public_name }}</strong></label>
</p> </p>
</div> </div>

View File

@@ -0,0 +1,6 @@
{% load static %}
{% load compress %}
{% compress js %}
<script type="text/javascript" src="{% static "pretixpresale/js/walletdetection.js" %}"></script>
{% endcompress %}

View File

@@ -3,6 +3,10 @@
{% load eventurl %} {% load eventurl %}
{% load money %} {% load money %}
{% block title %}{% trans "Change payment method" %}{% endblock %} {% block title %}{% trans "Change payment method" %}{% endblock %}
{% block custom_header %}
{{ block.super }}
{% include "pretixpresale/event/fragment_walletdetection_head.html" %}
{% endblock %}
{% block content %} {% block content %}
<h2> <h2>
{% blocktrans trimmed with code=order.code %} {% blocktrans trimmed with code=order.code %}
@@ -29,7 +33,8 @@
<input type="radio" name="payment" value="{{ p.provider.identifier }}" <input type="radio" name="payment" value="{{ p.provider.identifier }}"
data-parent="#payment_accordion" data-parent="#payment_accordion"
{% if selected == p.provider.identifier %}checked="checked"{% endif %} {% if selected == p.provider.identifier %}checked="checked"{% endif %}
data-toggle="radiocollapse" data-target="#payment_{{ p.provider.identifier }}" /> data-toggle="radiocollapse" data-target="#payment_{{ p.provider.identifier }}"
data-wallets="{{ p.provider.walletqueries|join:"|" }}"/>
<strong>{{ p.provider.public_name }}</strong> <strong>{{ p.provider.public_name }}</strong>
</label> </label>
</h4> </h4>

View File

@@ -0,0 +1,71 @@
'use strict';
var walletdetection = {
applepay: async function () {
// This is a weak check for Apple Pay - in order to do a proper check, we would need to also call
// canMakePaymentsWithActiveCard(merchantIdentifier)
return !!(window.ApplePaySession && window.ApplePaySession.canMakePayments());
},
googlepay: async function () {
// Checking for Google Pay is a little bit more involved, since it requires including the Google Pay JS SDK, and
// providing a lot of information.
// So for the time being, we only check if Google Pay is available in TEST-mode, which should hopefully give us a
// good enough idea if Google Pay could be present on this device; even though there are still a lot of other
// factors that could inhibit Google Pay from actually being offered to the customer.
return $.ajax({
url: 'https://pay.google.com/gp/p/js/pay.js',
dataType: 'script',
}).then(function() {
const paymentsClient = new google.payments.api.PaymentsClient({environment: 'TEST'});
return paymentsClient.isReadyToPay({
apiVersion: 2,
apiVersionMinor: 0,
allowedPaymentMethods: [{
type: 'CARD',
parameters: {
allowedAuthMethods: ["PAN_ONLY", "CRYPTOGRAM_3DS"],
allowedCardNetworks: ["AMEX", "DISCOVER", "INTERAC", "JCB", "MASTERCARD", "VISA"]
}
}],
})
}).then(function (response) {
return !!response.result;
});
},
name_map: {
applepay: gettext('Apple Pay'),
googlepay: gettext('Google Pay'),
}
}
$(function () {
const wallets = $('[data-wallets]')
.map(function(index, pm) {
return pm.getAttribute("data-wallets").split("|");
})
.get()
.flat()
.filter(function(item, pos, self) {
// filter out empty or duplicate values
return item && self.indexOf(item) == pos;
});
wallets.forEach(function(wallet) {
const labels = $('[data-wallets*='+wallet+'] + label strong, [data-wallets*='+wallet+'] + strong')
.append('<span class="wallet wallet-loading"> <i aria-hidden="true" class="fa fa-cog fa-spin"></i></span>')
walletdetection[wallet]()
.then(function(result) {
const spans = labels.find(".wallet-loading:nth-of-type(1)");
if (result) {
spans.removeClass('wallet-loading').hide().text(', ' + walletdetection.name_map[wallet]).fadeIn(300);
} else {
spans.remove();
}
})
.catch(function(result) {
labels.find(".wallet-loading:nth-of-type(1)").remove();
})
});
});

View File

@@ -179,3 +179,7 @@
flex: 1; flex: 1;
} }
} }
.wallet-loading + .wallet-loading {
display: none;
}