/** * * Apple Pay * * @author Presta-Module.com * @copyright Presta-Module * * ____ __ __ * | _ \ | \/ | * | |_) | | |\/| | * | __/ | | | | * |_| |_| |_| * ****/ var pmApplePayPlugin = { debug: false, paymentRequest: null, session: null, stripeChargeUrl: null, stripeEditedDetailsUrl: null, // Product details idProduct: null, idProductAttribute: null, productQuantity: 1, hasProductsInCart: false, clearCartConfirmationMessage: null, acceptTermsMessage: null, shippingContact: null, hasShippingMethods: true, hasAddressErrors: false, shippingMethodOverride: null, fromOrderLastStep: false, // Check if Apple Pay is available, and trigger showApplePayButtons() if so showApplePayButtonsIfAvailable: function() { if (pmApplePayPlugin.debug) { console.log('[Apple Pay][Call] showApplePayButtonsIfAvailable'); } Stripe.applePay.checkAvailability(function(applePayIsAvailable) { applePayCapableDevice = (window.ApplePaySession && ApplePaySession.canMakePayments()); if (applePayIsAvailable || applePayCapableDevice) { if (pmApplePayPlugin.debug) { if (applePayIsAvailable) { console.log('[Apple Pay] Available'); } else { console.warn('[Apple Pay] Available, but setup is needed on user device'); } } // Add a specific CSS class to body $('body').addClass('pm-applepay'); // Display the Apple Pay button pmApplePayPlugin.showApplePayButtons(!applePayIsAvailable && applePayCapableDevice); } else { if (pmApplePayPlugin.debug) { console.error('[Apple Pay] Unavailable (need Safari 10+ and a registered domain name)'); } } }); }, // Called when Apple Pay is available and can make payment on active card showApplePayButtons: function(setupNeeded) { if (pmApplePayPlugin.debug) { console.log('[Apple Pay][Call] showApplePayButtons'); } applePayButtons = $('.pm-apple-pay-button'); applePayContainers = $('.pm-apple-pay-container'); // Set button width to the same as confirmation button, if exists if ($('p.cart_navigation a.standard-checkout').width() > 150) { applePayButtons.css('width', $('p.cart_navigation a.standard-checkout').width()); } else if ($('.product #add_to_cart button').width() > 150) { applePayButtons.css('width', $('.product #add_to_cart button').width()); } applePayContainers.removeClass('hidden'); applePayContainers.parents('.product-miniature,.js-product-miniature').addClass('pm-with-apple-pay'); if (setupNeeded) { applePayButtons.addClass('pm-apple-pay-setup-button'); } $(document).off('click', '.pm-apple-pay-button').on('click', '.pm-apple-pay-button', function(e) { e.preventDefault(); // If we are on order last step, check if the terms and condition checkbox before letting the customer pay if (pmApplePayPlugin.fromOrderLastStep) { let selector = 'input[name="conditions_to_approve[terms-and-conditions]"]'; if (typeof (prestashop.selectors) !== 'undefined') { selector = prestashop.selectors.checkout.termsCheckboxSelector; } if (!$(selector).prop('checked')) { alert(pmApplePayPlugin.acceptTermsMessage); return; } } pmApplePayPlugin.payWithApplePay($(this)); }); }, // Update product count in cart so we can show alert if needed updateCartProductsCount: function(ajaxOptions) { if (typeof(ajaxOptions) == 'object' && typeof(ajaxOptions.responseJSON) == 'object' && typeof(ajaxOptions.responseJSON.products) == 'object' && typeof(ajaxOptions.responseJSON.nbTotalProducts) == 'number') { pmApplePayPlugin.hasProductsInCart = (ajaxOptions.responseJSON.nbTotalProducts > 0); } }, // Start a payment with Apple Pay payWithApplePay: function(sourceButton) { if (pmApplePayPlugin.debug) { console.log('[Apple Pay][Call] payWithApplePay'); } // Reset any previous data to default values pmApplePayPlugin.shippingMethodOverride = null; pmApplePayPlugin.shippingContact = null; pmApplePayPlugin.hasShippingMethods = true; pmApplePayPlugin.hasAddressErrors = false; // Set Stripe publishable API Key Stripe.setPublishableKey(pmApplePayPlugin.stripePublishableKey); // Retrieve paymentRequest pmApplePayPlugin.idProduct = $(sourceButton).data('id-product'); // Try to retrieve product informations first if (!isNaN(pmApplePayPlugin.idProduct)) { // Get id_product_attribute from button (product list) or product sheet pmApplePayPlugin.idProductAttribute = $(sourceButton).data('id-product-attribute'); if (isNaN(pmApplePayPlugin.idProductAttribute)) { pmApplePayPlugin.idProductAttribute = $('#idCombination').val(); if (isNaN(pmApplePayPlugin.idProductAttribute)) { pmApplePayPlugin.idProductAttribute = $('input[name="id_product_attribute"]').val(); if (isNaN(pmApplePayPlugin.idProductAttribute)) { pmApplePayPlugin.idProductAttribute = null; } } } pmApplePayPlugin.productQuantity = $('#quantity_wanted').val(); if (isNaN(pmApplePayPlugin.productQuantity)) { pmApplePayPlugin.productQuantity = $('input[name="qty"]').val(); } if (isNaN(pmApplePayPlugin.productQuantity) || pmApplePayPlugin.productQuantity <= 0) { pmApplePayPlugin.productQuantity = 1; } // Show alert if there is at least one product in cart if (pmApplePayPlugin.hasProductsInCart) { if (confirm(pmApplePayPlugin.clearCartConfirmationMessage)) { pmApplePayPlugin.paymentRequest = pmApplePayPlugin.getPaymentRequestDataFromProduct(); } else { return; } } else { pmApplePayPlugin.paymentRequest = pmApplePayPlugin.getPaymentRequestDataFromProduct(); } } else { // Retrieve shopping cart informations pmApplePayPlugin.paymentRequest = pmApplePayPlugin.getPaymentRequestData(null, null); } // Do not create Apple Pay session if paymentRequest is not filled (an error was displayed) if (pmApplePayPlugin.paymentRequest == null) { return; } // Build and begin Apple Pay session pmApplePayPlugin.session = Stripe.applePay.buildSession(pmApplePayPlugin.paymentRequest, pmApplePayPlugin.chargeCustomer, pmApplePayPlugin.stripeBuildSessionError); pmApplePayPlugin.session.begin(); // Handle needed Apple Pay events // Cancel action pmApplePayPlugin.session.addEventListener('cancel', function(event) { if (pmApplePayPlugin.debug) { console.log('[Apple Pay][Event] cancel'); console.log(event); } }); pmApplePayPlugin.session.addEventListener('couponcodechanged', function(event) { if (pmApplePayPlugin.debug) { console.log('[Apple Pay][Event] couponcodechanged'); console.log(event); } newPaymentRequestData = pmApplePayPlugin.getPaymentRequestData(null, null, event.couponCode); const update = { newTotal: newPaymentRequestData.total, newLineItems: newPaymentRequestData.lineItems, newShippingMethods: newPaymentRequestData.shippingMethods, } if (newPaymentRequestData['couponCodeError']) { // couponCodeInvalid, couponCodeExpired update.errors = [new ApplePayError(newPaymentRequestData['couponCodeError'])]; } pmApplePayPlugin.session.session.completeCouponCodeChange(update); }); // Shipping contact has changed pmApplePayPlugin.session.addEventListener('shippingcontactselected', function(event) { if (pmApplePayPlugin.debug) { console.log('[Apple Pay][Event] shippingcontactselected'); console.log(event); } newPaymentRequestData = pmApplePayPlugin.getPaymentRequestData(event.shippingContact, null); if (!pmApplePayPlugin.hasShippingMethods || pmApplePayPlugin.hasAddressErrors) { // Handle API V3 which has a dedicated error message for the unavailable shipping if (typeof (ApplePayError) !== 'undefined') { const ApplePayShippingContactUpdate = { newShippingMethods: newPaymentRequestData.shippingMethods, newTotal: newPaymentRequestData.total, newLineItems: newPaymentRequestData.lineItems, errors: [new ApplePayError('addressUnserviceable')], }; pmApplePayPlugin.session.completeShippingContactSelection(ApplePayShippingContactUpdate); } else { pmApplePayPlugin.session.completeShippingContactSelection(ApplePaySession.STATUS_INVALID_SHIPPING_POSTAL_ADDRESS, newPaymentRequestData.shippingMethods, newPaymentRequestData.total, newPaymentRequestData.lineItems); } } else { pmApplePayPlugin.session.completeShippingContactSelection(ApplePaySession.STATUS_SUCCESS, newPaymentRequestData.shippingMethods, newPaymentRequestData.total, newPaymentRequestData.lineItems); } }); // Shipping method has changed pmApplePayPlugin.session.addEventListener('shippingmethodselected', function(event) { if (pmApplePayPlugin.debug) { console.log('[Apple Pay][Event] shippingmethodselected'); console.log(event); } newPaymentRequestData = pmApplePayPlugin.getPaymentRequestData(null, event.shippingMethod.identifier); pmApplePayPlugin.session.completeShippingMethodSelection(ApplePaySession.STATUS_SUCCESS, newPaymentRequestData.total, newPaymentRequestData.lineItems); }); }, // Retrieve paymentRequest used by Apple Pay getPaymentRequestData: function(shippingContact, carrierIdentifier, couponCode = null) { if (pmApplePayPlugin.debug) { console.log('[Apple Pay][Call] getPaymentRequestData'); console.log(shippingContact); console.log(carrierIdentifier); console.log(couponCode); } if (typeof(shippingContact) == 'object' && shippingContact != null) { pmApplePayPlugin.shippingContact = shippingContact; } $.ajax({ type: 'POST', url: pmApplePayPlugin.stripeEditedDetailsUrl, data: { shipping_contact: (typeof(pmApplePayPlugin.shippingContact) == 'object' ? JSON.stringify(pmApplePayPlugin.shippingContact) : null), carrier_identifier: (typeof(carrierIdentifier) != 'undefined' ? carrierIdentifier : null), coupon_code: (typeof(couponCode) != 'undefined' ? couponCode : null), from_order_last_step: pmApplePayPlugin.fromOrderLastStep ? 1 : 0, }, async: false, timeout: 120000, dataType: 'json', }).always(function(data, textStatus, errorThrown) { if (typeof(data) == 'object') { if (data.result == true) { // Update newPaymentRequest global variable pmApplePayPlugin.paymentRequest = data.paymentRequest; pmApplePayPlugin.hasShippingMethods = data.hasShippingMethods; pmApplePayPlugin.hasAddressErrors = data.hasAddressErrors; pmApplePayPlugin.stripeChargeUrl = data.stripeChargeUrl; if (pmApplePayPlugin.hasShippingMethods) { pmApplePayPlugin.shippingMethodOverride = pmApplePayPlugin.paymentRequest.shippingMethods[0]; } else { pmApplePayPlugin.shippingMethodOverride = null; } } else { // Show error if (typeof(data.error) != 'undefined' && data.error.length) { // Show error to the customer alert(data.error); if (typeof(data.redirectURL) != 'undefined' && data.redirectURL.length) { window.location = data.redirectURL; } } } } else { if (pmApplePayPlugin.debug) { console.error('[Apple Pay] Error while retrieving Payment Request'); } pmApplePayPlugin.paymentRequest = null; pmApplePayPlugin.session.abort(); } }); return pmApplePayPlugin.paymentRequest; }, // Retrieve paymentRequest used by Apple Pay getPaymentRequestDataFromProduct: function() { if (pmApplePayPlugin.debug) { console.log('[Apple Pay][Call] getPaymentRequestDataFromProduct'); } $.ajax({ type: 'POST', url: pmApplePayPlugin.stripeBuyProductUrl, data: { id_product: pmApplePayPlugin.idProduct, id_product_attribute: (typeof(pmApplePayPlugin.idProductAttribute) != 'undefined' && !isNaN(pmApplePayPlugin.idProductAttribute) ? pmApplePayPlugin.idProductAttribute : 0), quantity: (typeof(pmApplePayPlugin.productQuantity) != 'undefined' && !isNaN(pmApplePayPlugin.productQuantity) && pmApplePayPlugin.productQuantity > 0 ? pmApplePayPlugin.productQuantity : 1), }, async: false, timeout: 120000, dataType: 'json', }).always(function(data, textStatus, errorThrown) { if (typeof(data) == 'object') { if (data.result == true) { // Update paymentRequest variable pmApplePayPlugin.paymentRequest = data.paymentRequest; pmApplePayPlugin.hasShippingMethods = data.hasShippingMethods; pmApplePayPlugin.stripeChargeUrl = data.stripeChargeUrl; pmApplePayPlugin.stripeEditedDetailsUrl = data.stripeEditedDetailsUrl; } else { // Show error if (typeof(data.error) != 'undefined' && data.error.length) { // Show error to the customer alert(data.error); if (typeof(data.redirectURL) != 'undefined' && data.redirectURL.length) { window.location = data.redirectURL; } } } } else { if (pmApplePayPlugin.debug) { console.error('[Apple Pay] Error while retrieving Payment Request'); } pmApplePayPlugin.paymentRequest = null; pmApplePayPlugin.session.abort(); } }); return pmApplePayPlugin.paymentRequest; }, // Action when customer is trying to pay chargeCustomer: function(payment, callback) { if (pmApplePayPlugin.debug) { console.log('[Apple Pay][Call] chargeCustomer'); console.log(payment); } finalShippingMethod = null; if (pmApplePayPlugin.shippingMethodOverride != null) { finalShippingMethod = pmApplePayPlugin.shippingMethodOverride; } else if (typeof(payment.shippingMethod) == 'object') { finalShippingMethod = payment.shippingMethod; } $.ajax({ type: 'POST', url: pmApplePayPlugin.stripeChargeUrl, data: { stripe_token: (typeof(payment.token) == 'object' ? JSON.stringify(payment.token) : null), shipping_contact: (typeof(payment.shippingContact) == 'object' && ((typeof(pmApplePayPlugin.paymentRequest.isPickup) !== 'undefined' && !pmApplePayPlugin.paymentRequest.isPickup) || typeof(pmApplePayPlugin.paymentRequest.isPickup) == 'undefined') ? JSON.stringify(payment.shippingContact) : null), shipping_method: (typeof(finalShippingMethod) == 'object' ? JSON.stringify(finalShippingMethod) : null), fromOrderLastStep: pmApplePayPlugin.fromOrderLastStep ? 1 : 0, }, async: true, timeout: 120000, dataType: 'json', }).always(function(data, textStatus, errorThrown) { if (pmApplePayPlugin.debug) { console.log('[Apple Pay][Call] chargeCustomer answer:'); console.log(data); } if (typeof(data) == 'object') { if (data.result == true) { callback(ApplePaySession.STATUS_SUCCESS); // Redirect to confirmation page... window.location = data.confirmationURL; } else { if (typeof(data.error) != 'undefined' && data.error.length) { // Payment or customer creation failed... callback(ApplePaySession.STATUS_FAILURE); // Show error to the customer alert(data.error); if (typeof(data.redirectURL) != 'undefined' && data.redirectURL.length) { window.location = data.redirectURL; } } } } else { // Failed callback(ApplePaySession.STATUS_FAILURE); } }); }, // Action if Stripe is not able to start the session (API Error, Apple Pay error ?) stripeBuildSessionError: function(error) { alert('Stripe error: ' + error.message); }, } // Once this file has been loaded, we can emit our custom ready event // This ensures we can work with a shop having a `defer` attribute on the scripts inclusions function dispatchPmApplePayEvent() { const pmApplePayCustomReadyEvent = new CustomEvent('pmApplePayPluginReady', { detail: pmApplePayPlugin }); document.dispatchEvent(pmApplePayCustomReadyEvent); } // If the shop doesn't use `defer` in the scripts tags, we must emit our custom event here instead of in the plugin // file, otherwise we'll never init (because the plugin would be loaded before this template at the bottom of the body) if (typeof (window.Stripe) !== 'undefined') { dispatchPmApplePayEvent(); } else { Object.defineProperty(window, 'Stripe', { get: function () { return this.value }, set: function (v) { this.value = v; // Apply a setTimeout because we need the set function to return for the new value to be "effective" setTimeout(() => dispatchPmApplePayEvent(), 1); } }); }