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 = {
'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'"],
'frame-src': ['{static}', 'https://checkout.stripe.com', 'https://js.stripe.com'],
'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__)
class WalletQueries:
APPLEPAY = 'applepay'
GOOGLEPAY = 'googlepay'
WALLETS = (
(APPLEPAY, pgettext_lazy('payment', 'Apple Pay')),
(GOOGLEPAY, pgettext_lazy('payment', 'Google Pay')),
)
class PaymentProviderForm(Form):
def clean(self):
cleaned_data = super().clean()
@@ -436,6 +446,16 @@ class BasePaymentProvider:
d['_restrict_to_sales_channels']._as_type = list
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):
"""
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.forms import SecretKeySettingsField
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.services.mail import SendMailException
from pretix.base.settings import SettingsSandbox
@@ -747,6 +747,11 @@ class StripeCC(StripeMethod):
public_name = _('Credit card')
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:
account = get_stripe_account_key(self)
if not RegisteredApplePayDomain.objects.filter(account=account, domain=request.host).exists():
@@ -3,6 +3,10 @@
{% load money %}
{% load bootstrap3 %}
{% load rich_text %}
{% block custom_header %}
{{ block.super }}
{% include "pretixpresale/event/fragment_walletdetection_head.html" %}
{% endblock %}
{% block inner %}
{% if current_payments %}
<p>{% trans "You already selected the following payment methods:" %}</p>
@@ -71,7 +75,8 @@
{% if selected == p.provider.identifier %}checked="checked"{% endif %}
id="input_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>
</p>
</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 money %}
{% block title %}{% trans "Change payment method" %}{% endblock %}
{% block custom_header %}
{{ block.super }}
{% include "pretixpresale/event/fragment_walletdetection_head.html" %}
{% endblock %}
{% block content %}
<h2>
{% blocktrans trimmed with code=order.code %}
@@ -29,7 +33,8 @@
<input type="radio" name="payment" value="{{ p.provider.identifier }}"
data-parent="#payment_accordion"
{% 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>
</label>
</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);
});
});
});