PProv: Implement detection of wallets such as Google Pay and Apple Pay

This commit is contained in:
Martin Gross
2023-06-30 10:21:22 +02:00
parent 3717c4b553
commit 1b1c4358d3
7 changed files with 165 additions and 4 deletions
+1 -1
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}"],
+20
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,16 @@ 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):
"""
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.
+6 -1
View File
@@ -59,7 +59,7 @@ 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
@@ -747,6 +747,11 @@ 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
return [WalletQueries.APPLEPAY, WalletQueries.GOOGLEPAY]
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():
@@ -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>
@@ -0,0 +1,6 @@
{% load static %}
{% load compress %}
{% compress js %}
<script type="text/javascript" src="{% static "pretixpresale/js/walletdetection.js" %}"></script>
{% endcompress %}
@@ -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>
@@ -0,0 +1,120 @@
'use strict';
var walletdetection = {
applepay: 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: 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.
const baseRequest = {
apiVersion: 2,
apiVersionMinor: 0
};
const tokenizationSpecification = {
type: 'PAYMENT_GATEWAY',
parameters: {
'gateway': 'example',
'gatewayMerchantId': 'exampleGatewayMerchantId'
}
};
const allowedCardNetworks = ["AMEX", "DISCOVER", "INTERAC", "JCB", "MASTERCARD", "VISA"];
const allowedCardAuthMethods = ["PAN_ONLY", "CRYPTOGRAM_3DS"];
const baseCardPaymentMethod = {
type: 'CARD',
parameters: {
allowedAuthMethods: allowedCardAuthMethods,
allowedCardNetworks: allowedCardNetworks
}
};
const cardPaymentMethod = Object.assign(
{tokenizationSpecification: tokenizationSpecification},
baseCardPaymentMethod
);
return $.ajax({
url: 'https://pay.google.com/gp/p/js/pay.js',
dataType: 'script',
success: function () {
const paymentsClient = new google.payments.api.PaymentsClient({environment: 'TEST'});
const isReadyToPayRequest = Object.assign({}, baseRequest);
isReadyToPayRequest.allowedPaymentMethods = [baseCardPaymentMethod];
paymentsClient.isReadyToPay(isReadyToPayRequest)
.then(function (response) {
if (response.result) {
return true;
}
})
.catch(function (err) {
return false;
});
},
});
},
name_map: {
applepay: gettext('Apple Pay'),
googlepay: gettext('Google Pay'),
}
}
$(function () {
let requestedWallets = Array();
let paymentMethods = $('[data-wallets][data-wallets!=""]');
paymentMethods.each(function () {
let $s = $(this);
requestedWallets = requestedWallets.concat($s.data("wallets").split("|"));
})
// Filtering out any doubles
requestedWallets = new Set(requestedWallets);
// Perform the actual check *only* on the requested wallets, if they are supported by the browser
let availableWallets = Array();
requestedWallets.forEach(function (it) {
if (walletdetection[it]()) {
availableWallets.push(it);
}
});
paymentMethods.each(function () {
let $s = $(this);
let wallets = Array();
// Run the translation on the available wallet strings before pushing them out.
$s.data("wallets").split("|").forEach(function (it) {
if (availableWallets.includes(it)) {
wallets.push(gettext(walletdetection.name_map[it]));
}
})
// In case there is no wallets available, we do not want to flicker the screen
if (wallets.length === 0) {
return;
}
// The first selector is used on the regular payment-step of the checkout flow
// The second selector is used for the payment method change view.
// In the long run, the layout of both pages should be adjusted to be one.
let textselector = $s.next('label').find('strong');
let textselector2 = $s.next("strong");
textselector.fadeOut(300, function () {
wallets.unshift(textselector.text());
textselector.text(wallets.join(", "));
textselector.fadeIn(300);
});
textselector2.fadeOut(300, function () {
wallets.unshift(textselector2.text());
textselector2.text(wallets.join(", "));
textselector2.fadeIn(300);
});
});
});