Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

# Changelog #

## Changes in release 6.4.6
+ Apple Pay now works in Chrome, Edge and Firefox on desktop via Apple's iOS 18 QR code flow, using Apple's official Apple Pay JS SDK
+ Fixed Apple Pay Direct quoting shipping prices from the shop default country's zone instead of the customer's delivery zone
+ Fixed the product page quantity selector getting squeezed by the Apple Pay Direct button on narrow screens; the button now wraps onto its own line

## Changes in release 6.4.5
+ New payment method: Wero, available on the Payments API
+ New payment method: Billink, available on the Payments API for consumer purchases up to 2,500.00 EUR
Expand Down
2 changes: 1 addition & 1 deletion controllers/front/applePayDirectAjax.php
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ private function getTotalApplePayCartPrice()

$this->ajaxRender(json_encode(
[
'total' => $cart->getOrderTotal(),
'total' => number_format($cart->getOrderTotal(), 2, '.', ''),
]
));
}
Expand Down
8 changes: 8 additions & 0 deletions mollie.php
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,13 @@ public function hookActionFrontControllerSetMedia($params)

$canDisplayInProductPage = $controller instanceof ProductControllerCore && $isApplePayDirectProductEnabled;
$canDisplayInCartPage = $controller instanceof CartControllerCore && $isApplePayDirectCartEnabled;
if ($canDisplayInProductPage || $canDisplayInCartPage) {
$this->context->controller->registerJavascript(
'mollie-apple-pay-sdk',
'https://applepay.cdn-apple.com/jsapi/1.latest/apple-pay-sdk.js',
['server' => 'remote', 'position' => 'head', 'priority' => 10, 'attributes' => 'defer']
);
}

if (!$canDisplayInProductPage && !$canDisplayInCartPage) {
return;
Expand All @@ -475,6 +482,7 @@ public function hookActionFrontControllerSetMedia($params)
'ajaxUrl' => $this->context->link->getModuleLink('mollie', 'applePayDirectAjax'),
'cartId' => $this->context->cart->id,
'applePayButtonStyle' => (int) $configuration->get(Config::MOLLIE_APPLE_PAY_DIRECT_STYLE),
'applePayLocale' => $this->context->language->locale,
]);

$this->context->controller->addCSS($this->getPathUri() . 'views/css/front/apple_pay_direct.css');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ private function createMollieTransaction(Cart $cart, string $cardToken)
$paymentMethodObj = new MolPaymentMethod((int) $paymentMethodId);

$paymentData = $this->paymentMethodService->getPaymentData(
$cart->getOrderTotal(true, Cart::BOTH, null, $cart->id_carrier),
$cart->getOrderTotal(true, Cart::BOTH),
Tools::strtoupper($currency->iso_code),
Config::APPLEPAY,
(int) $cart->id,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,18 @@ public function handle(UpdateApplePayShippingContact $command): array

$applePayCarriers = $this->applePayCarriersBuilder->build(Carrier::getCarriersForOrder($country->id_zone), $country->id_zone);

// the sheet preselects the first listed method without firing onshippingmethodselected,
// so the cart must already carry it or the authorized total diverges from the charged one
$firstCarrier = $applePayCarriers[0] ?? null;

if ($firstCarrier) {
$cart->id_carrier = $firstCarrier->getCarrierId();
$cart->setDeliveryOption([
$cart->id_address_delivery => $firstCarrier->getCarrierId() . ',',
]);
$cart->update();
}

$shippingMethods = ShippingMethodUtility::collectShippingMethodData($applePayCarriers, $cart);
$totals = $this->orderTotalCollector->getOrderTotals($applePayCarriers, $cart);

Expand All @@ -86,7 +98,7 @@ public function handle(UpdateApplePayShippingContact $command): array
'totals' => $totals,
'paymentFee' => [
'label' => 'Payment fee',
'amount' => $paymentFee,
'amount' => number_format($paymentFee, 2, '.', ''),
'type' => 'final',
],
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ public function handle(UpdateApplePayShippingMethod $command): array

$cart->update();

$orderTotal = (float) $cart->getOrderTotal(true, Cart::BOTH, null, $command->getCarrierId());
$orderTotal = (float) $cart->getOrderTotal(true, Cart::BOTH);

$paymentFeeData = $this->orderPaymentFeeService->getPaymentFee($orderTotal, Config::APPLEPAY);

Expand All @@ -75,7 +75,7 @@ public function handle(UpdateApplePayShippingMethod $command): array
return [
'success' => true,
'data' => [// TODO use calculator
'amount' => $orderTotal + $paymentFee,
'amount' => number_format($orderTotal + $paymentFee, 2, '.', ''),
],
];
}
Expand Down
25 changes: 18 additions & 7 deletions src/Collector/ApplePayDirect/OrderTotalCollector.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,20 @@ public function __construct(OrderPaymentFeeService $orderPaymentFeeService)
*/
public function getOrderTotals($applePayCarriers, Cart $cart)
{
return array_map(function (AppleCarrier $carrier) use ($cart) {
$originalDeliveryOption = $cart->delivery_option;

// Apple Pay addresses are soft-deleted, which fails Customer::customerHasAddress inside
// getPackageShippingCost, so explicit-carrier totals price shipping against the
// default-country zone; pricing through the cart's delivery option resolves the real zone
$totals = array_map(function (AppleCarrier $carrier) use ($cart) {
// raw assignment on purpose: Cart::setDeliveryOption() fires CartRule::autoRemoveFromCart(),
// which deletes vouchers from the shopper's real cart while this loop is only quoting
$cart->delivery_option = json_encode([
$cart->id_address_delivery => $carrier->getCarrierId() . ',',
]);

$orderTotal = (float) number_format(
$cart->getOrderTotal(
true,
Cart::BOTH,
null,
$carrier->getCarrierId()),
$cart->getOrderTotal(true, Cart::BOTH),
2,
'.',
''
Expand All @@ -59,9 +66,13 @@ public function getOrderTotals($applePayCarriers, Cart $cart)
return [
'type' => 'final',
'label' => $carrier->getName(),
'amount' => $orderTotal + $paymentFee,
'amount' => number_format($orderTotal + $paymentFee, 2, '.', ''),
'amountWithoutFee' => $orderTotal,
];
}, $applePayCarriers);

$cart->delivery_option = $originalDeliveryOption;

return $totals;
}
}
20 changes: 17 additions & 3 deletions src/Utility/ApplePayDirect/ShippingMethodUtility.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,27 @@ class ShippingMethodUtility
*/
public static function collectShippingMethodData(array $carriers, Cart $cart)
{
return array_map(function (AppleCarrier $carrier) use ($cart) {
$originalDeliveryOption = $cart->delivery_option;

// same zone-fallback workaround as OrderTotalCollector: soft-deleted Apple Pay addresses
// make explicit-carrier totals price shipping against the default-country zone
$shippingMethods = array_map(function (AppleCarrier $carrier) use ($cart) {
// raw assignment on purpose: Cart::setDeliveryOption() fires CartRule::autoRemoveFromCart(),
// which deletes vouchers from the shopper's real cart while this loop is only quoting
$cart->delivery_option = json_encode([
$cart->id_address_delivery => $carrier->getCarrierId() . ',',
]);

return [
'identifier' => $carrier->getCarrierId(),
'identifier' => (string) $carrier->getCarrierId(),
'label' => $carrier->getName(),
'amount' => number_format($cart->getOrderTotal(true, Cart::ONLY_SHIPPING, null, $carrier->getCarrierId()), 2, '.', ''),
'amount' => number_format($cart->getOrderTotal(true, Cart::ONLY_SHIPPING), 2, '.', ''),
'detail' => $carrier->getDelay(),
];
}, $carriers);

$cart->delivery_option = $originalDeliveryOption;

return $shippingMethods;
}
}
20 changes: 18 additions & 2 deletions views/css/front/apple_pay_direct.css
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,27 @@
}
}

apple-pay-button {
--apple-pay-button-width: 200px;
--apple-pay-button-height: 32px;
--apple-pay-button-border-radius: 5px;
--apple-pay-button-padding: 0px;
display: inline-block;
}

#mollie-applepay-direct-button {
padding: 5px;
}

/* classic theme keeps this row nowrap, so a tight fit crushes the quantity
spinner instead of moving the whole button to the next line */
.product-quantity {
flex-wrap: wrap;
}

/* Hummingbird theme (PS9) - product page */
.product__add-to-cart #mollie-applepay-direct-button .apple-pay-button {
.product__add-to-cart #mollie-applepay-direct-button .apple-pay-button,
.product__add-to-cart #mollie-applepay-direct-button apple-pay-button {
clip-path: inset(0 round 4px);
}

Expand All @@ -64,6 +79,7 @@
margin-top: 0.5rem;
}

.cart-detailed__actions #mollie-applepay-direct-button .apple-pay-button {
.cart-detailed__actions #mollie-applepay-direct-button .apple-pay-button,
.cart-detailed__actions #mollie-applepay-direct-button apple-pay-button {
clip-path: inset(0 round 4px);
}
101 changes: 89 additions & 12 deletions views/js/front/applePayDirect/applePayDirectCart.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,23 @@
*/

$(document).ready(function () {
whenApplePaySessionAvailable(initApplePayDirect)
})

function initApplePayDirect() {
var applePayMethodElement = document.querySelector(
'#mollie-applepay-direct-button',
)

const canShowButton = applePayMethodElement && (window.ApplePaySession && ApplePaySession.canMakePayments())
if (!canShowButton) {
if (!applePayMethodElement) {
return;
}

let buttonStyle = getApplePayButtonStyle();
createAppleButton(applePayMethodElement, buttonStyle)
const startApplePaySession = function () {
applePaySession();
}
createAppleButton(applePayMethodElement, buttonStyle, startApplePaySession)
toggleApplePayVisibility()

if (typeof prestashop !== 'undefined') {
Expand All @@ -31,7 +37,7 @@ $(document).ready(function () {
}

if (!container.querySelector('#mollie_applepay_button')) {
createAppleButton(container, buttonStyle);
createAppleButton(container, buttonStyle, startApplePaySession);
}

toggleApplePayVisibility()
Expand All @@ -42,11 +48,6 @@ $(document).ready(function () {
let selectedShippingMethod = []
let cartSubTotal = 0;

$(document).on('click', '#mollie_applepay_button', function(e) {
e.preventDefault();
applePaySession();
})

let applePaySession = () => {
getCartSubTotal();
//todo: constant
Expand Down Expand Up @@ -207,7 +208,31 @@ $(document).ready(function () {
},
})
}
});
}

function whenApplePaySessionAvailable(onAvailable) {
if (canUseApplePaySession()) {
onAvailable()

return
}

if (!window.customElements) {
return
}

// insurance: 1.latest is a rolling URL; if Apple ever moves the ApplePaySession
// polyfill behind the SDK's dynamic import, re-check once the module lands
customElements.whenDefined('apple-pay-button').then(function () {
if (canUseApplePaySession()) {
onAvailable()
}
})
}

function canUseApplePaySession() {
return !!(window.ApplePaySession && window.ApplePaySession.canMakePayments())
}

function getApplePayButtonStyle() {
switch (parseInt(applePayButtonStyle)) {
Expand Down Expand Up @@ -276,12 +301,64 @@ function getUrlParam(sParam, string) {
}
}

function createAppleButton(ApplePayButtonElement, buttonStyle) {
function createAppleButton(ApplePayButtonElement, buttonStyle, onClick) {
if (!window.customElements) {
ApplePayButtonElement.appendChild(createLegacyAppleButton(buttonStyle, onClick))

return
}

const button = document.createElement('apple-pay-button')
button.setAttribute('id', 'mollie_applepay_button')
button.setAttribute('buttonstyle', getApplePaySdkButtonStyle())
button.setAttribute('type', 'plain')
if (typeof applePayLocale !== 'undefined') {
button.setAttribute('locale', applePayLocale)
}
bindAppleButtonClick(button, onClick)
ApplePayButtonElement.appendChild(button)

// the SDK registers apple-pay-button asynchronously; swap to the legacy button if it never arrives
const legacyButtonTimeout = setTimeout(function () {
if (customElements.get('apple-pay-button')) {
return
}

button.replaceWith(createLegacyAppleButton(buttonStyle, onClick))
}, 3000)

customElements.whenDefined('apple-pay-button').then(function () {
clearTimeout(legacyButtonTimeout)
})
}

function createLegacyAppleButton(buttonStyle, onClick) {
const button = document.createElement('button')
button.setAttribute('id', 'mollie_applepay_button')
button.classList.add('apple-pay-button')
button.classList.add(buttonStyle)
ApplePayButtonElement.appendChild(button)
bindAppleButtonClick(button, onClick)

return button
}

// the SDK button swallows click propagation, so delegated handlers never fire - bind on the element itself
function bindAppleButtonClick(button, onClick) {
button.addEventListener('click', function (e) {
e.preventDefault()
onClick()
})
}

function getApplePaySdkButtonStyle() {
switch (parseInt(applePayButtonStyle)) {
case 1:
return 'white-outline';
case 2:
return 'white';
default:
return 'black';
}
}

function toggleApplePayVisibility() {
Expand Down
Loading
Loading