From 3ae22b3356e805003f5e3e3d36f81426b8645684 Mon Sep 17 00:00:00 2001 From: carmenmaymo Date: Mon, 31 Jul 2023 17:08:42 +0200 Subject: [PATCH] Add apple transaction in product before auth --- .../resources/js/Helper/applePayError.js | 10 + .../resources/js/Helper/applePayRequest.js | 22 + .../resources/js/applepayDirect.js | 259 +++++++++ modules/ppcp-applepay/services.php | 24 +- modules/ppcp-applepay/src/ApplepayModule.php | 30 +- .../src/ApplepayPaymentMethod.php | 495 +++++++++++++++++- .../src/DataToAppleButtonScripts.php | 138 +++++ .../src/PropertiesDictionary.php | 37 +- modules/ppcp-applepay/yarn.lock | 97 +++- .../ppcp-button/src/Assets/SmartButton.php | 3 +- 10 files changed, 1041 insertions(+), 74 deletions(-) create mode 100644 modules/ppcp-applepay/resources/js/Helper/applePayError.js create mode 100644 modules/ppcp-applepay/resources/js/Helper/applePayRequest.js create mode 100644 modules/ppcp-applepay/resources/js/applepayDirect.js create mode 100644 modules/ppcp-applepay/src/DataToAppleButtonScripts.php diff --git a/modules/ppcp-applepay/resources/js/Helper/applePayError.js b/modules/ppcp-applepay/resources/js/Helper/applePayError.js new file mode 100644 index 000000000..8775eea4e --- /dev/null +++ b/modules/ppcp-applepay/resources/js/Helper/applePayError.js @@ -0,0 +1,10 @@ +export function createAppleErrors(errors) { + const errorList = [] + for (const error of errors) { + const {contactField = null, code = null, message = null} = error + const appleError = contactField ? new ApplePayError(code, contactField, message) : new ApplePayError(code) + errorList.push(appleError) + } + + return errorList +} diff --git a/modules/ppcp-applepay/resources/js/Helper/applePayRequest.js b/modules/ppcp-applepay/resources/js/Helper/applePayRequest.js new file mode 100644 index 000000000..a690b6b2c --- /dev/null +++ b/modules/ppcp-applepay/resources/js/Helper/applePayRequest.js @@ -0,0 +1,22 @@ +export const request = (countryCode, currencyCode, totalLabel, subtotal) => { + return { + countryCode: countryCode, + currencyCode: currencyCode, + supportedNetworks: ['amex', 'maestro', 'masterCard', 'visa', 'vPay'], + merchantCapabilities: ['supports3DS'], + shippingType: 'shipping', + requiredBillingContactFields: [ + 'postalAddress', + 'email' + ], + requiredShippingContactFields: [ + 'postalAddress', + 'email' + ], + total: { + label: totalLabel, + amount: subtotal, + type: 'final' + } + } +} diff --git a/modules/ppcp-applepay/resources/js/applepayDirect.js b/modules/ppcp-applepay/resources/js/applepayDirect.js new file mode 100644 index 000000000..220475ba7 --- /dev/null +++ b/modules/ppcp-applepay/resources/js/applepayDirect.js @@ -0,0 +1,259 @@ +//import {useEffect, useState} from '@wordpress/element'; +import {createAppleErrors} from './Helper/applePayError.js'; +import {maybeShowButton} from './Helper/maybeShowApplePayButton.js'; +import {request} from './Helper/applePayRequest.js'; +import {buttonID, endpoints} from "./Helper/utils"; +import {loadPaypalScript} from '../../../ppcp-button/resources/js/modules/Helper/ScriptLoading' +( + function ({ wc_ppcp_applepay, jQuery}) { + document.addEventListener( + 'DOMContentLoaded', + () => { + if (PayPalCommerceGateway) { + let bootstrapped = false; + const {product: {id, needShipping = true, isVariation = false, price, stock}, shop: {countryCode, currencyCode = 'EUR', totalLabel = ''}, ajaxUrl} = wc_ppcp_applepay + + if (!id || !price || !countryCode || !ajaxUrl) { + return + } + let outOfStock = stock === 'outofstock' + if(outOfStock || !maybeShowButton()){ + return; + } + const nonce = document.getElementById('woocommerce-process-checkout-nonce').value + let productId = id + let productQuantity = 1 + let updatedContactInfo = [] + let selectedShippingMethod = [] + let redirectionUrl = '' + document.querySelector('input.qty').addEventListener('change', event => { + productQuantity = event.currentTarget.value + }) + function disableButton(appleButton) { + appleButton.disabled = true; + appleButton.classList.add("buttonDisabled"); + } + function enableButton(appleButton) { + appleButton.disabled = false; + appleButton.classList.remove("buttonDisabled"); + } + if (isVariation) { + let appleButton = document.querySelector('#applepay-container'); + jQuery('.single_variation_wrap').on('hide_variation', function (event, variation) { + disableButton(appleButton); + return; + }); + jQuery('.single_variation_wrap').on('show_variation', function (event, variation) { + // Fired when the user selects all the required dropdowns / attributes + // and a final variation is selected / shown + if (!variation.is_in_stock) { + disableButton(appleButton); + return; + } + if (variation.variation_id) { + productId = variation.variation_id + } + enableButton(appleButton); + }); + disableButton(appleButton); + } + const amountWithoutTax = productQuantity * price + loadPaypalScript(PayPalCommerceGateway, () => { + bootstrapped = true; + //print the button + const applepay = paypal.Applepay(); + applepay.config() + .then(applepayConfig => { + const appleContainer = document.getElementById("applepay-container"); + if (applepayConfig.isEligible) { + appleContainer.innerHTML = ''; + //handle transaction + const paymentRequest = { + countryCode: applepayConfig.countryCode, + merchantCapabilities: + applepayConfig.merchantCapabilities, + supportedNetworks: applepayConfig.supportedNetworks, + currencyCode: currencyCode, + requiredShippingContactFields: ["name", "phone", + "email", "postalAddress"], + requiredBillingContactFields: ["name", "phone", "email", + "postalAddress"], + total: { + label: totalLabel, + type: "final", + amount: amountWithoutTax, + }} + console.log(paymentRequest) + console.log(wc_ppcp_applepay) + let applePaySession = () => { + const session = new ApplePaySession(4, paymentRequest) + session.begin() + if(needShipping){ + session.onshippingmethodselected = function (event) { + jQuery.ajax({ + url: ajaxUrl, + method: 'POST', + data: { + action: 'ppcp_update_shipping_method', + shipping_method: event.shippingMethod, + product_id: productId, + caller_page: 'productDetail', + product_quantity: productQuantity, + simplified_contact: updatedContactInfo, + 'woocommerce-process-checkout-nonce': nonce, + }, + complete: (jqXHR, textStatus) => { + }, + success: (applePayShippingMethodUpdate, textStatus, jqXHR) => { + let response = applePayShippingMethodUpdate.data + console.log('onshippingmethod', response) + selectedShippingMethod = event.shippingMethod + console.log(selectedShippingMethod) + //order the response shipping methods, so that the selected shipping method is the first one + let orderedShippingMethods = response.newShippingMethods.sort((a, b) => { + if (a.label === selectedShippingMethod.label) { + return -1 + } + return 1 + }) + //update the response.newShippingMethods with the ordered shipping methods + response.newShippingMethods = orderedShippingMethods + if (applePayShippingMethodUpdate.success === false) { + response.errors = createAppleErrors(response.errors) + } + this.completeShippingMethodSelection(response) + }, + error: (jqXHR, textStatus, errorThrown) => { + console.warn(textStatus, errorThrown) + session.abort() + }, + }) + } + session.onshippingcontactselected = function (event) { + jQuery.ajax({ + url: ajaxUrl, + method: 'POST', + data: { + action: 'ppcp_update_shipping_contact', + product_id: productId, + caller_page: 'productDetail', + product_quantity: productQuantity, + simplified_contact: event.shippingContact, + need_shipping: needShipping, + 'woocommerce-process-checkout-nonce': nonce, + }, + complete: (jqXHR, textStatus) => { + }, + success: (applePayShippingContactUpdate, textStatus, jqXHR) => { + let response = applePayShippingContactUpdate.data + updatedContactInfo = event.shippingContact + console.log('onshippingcontact', response) + if (applePayShippingContactUpdate.success === false) { + response.errors = createAppleErrors(response.errors) + } + if (response.newShippingMethods) { + selectedShippingMethod = response.newShippingMethods[0] + } + this.completeShippingContactSelection(response) + }, + error: (jqXHR, textStatus, errorThrown) => { + console.warn(textStatus, errorThrown) + session.abort() + }, + }) + } + } + session.onvalidatemerchant = (applePayValidateMerchantEvent) => { + applepay.validateMerchant({ + validationUrl: applePayValidateMerchantEvent.validationURL + }) + .then(validateResult => { + session.completeMerchantValidation(validateResult.merchantSession); + console.log('validated') + }) + .catch(validateError => { + console.error(validateError); + session.abort(); + }); + }; + /*session.onpaymentauthorized = (ApplePayPayment) => { + const {billingContact, shippingContact } = ApplePayPayment.payment + + jQuery.ajax({ + url: ajaxUrl, + method: 'POST', + data: { + action: 'mollie_apple_pay_create_order', + productId: productId, + productQuantity: productQuantity, + shippingContact: ApplePayPayment.payment.shippingContact, + billingContact: ApplePayPayment.payment.billingContact, + token: ApplePayPayment.payment.token, + shippingMethod: selectedShippingMethod, + 'mollie-payments-for-woocommerce_issuer_applepay': 'applepay', + 'woocommerce-process-checkout-nonce': nonce, + 'billing_first_name': billingContact.givenName || '', + 'billing_last_name' : billingContact.familyName || '', + 'billing_company': '', + 'billing_country' : billingContact.countryCode || '', + 'billing_address_1' : billingContact.addressLines[0] || '', + 'billing_address_2' : billingContact.addressLines[1] || '', + 'billing_postcode' : billingContact.postalCode || '', + 'billing_city': billingContact.locality || '', + 'billing_state' : billingContact.administrativeArea || '', + 'billing_phone' : billingContact.phoneNumber || '000000000000', + 'billing_email' : shippingContact.emailAddress || '', + 'shipping_first_name': shippingContact.givenName || '', + 'shipping_last_name' : shippingContact.familyName || '', + 'shipping_company': '', + 'shipping_country' : shippingContact.countryCode || '', + 'shipping_address_1' : shippingContact.addressLines[0] || '', + 'shipping_address_2' : shippingContact.addressLines[1] || '', + 'shipping_postcode' : shippingContact.postalCode || '', + 'shipping_city': shippingContact.locality || '', + 'shipping_state' : shippingContact.administrativeArea || '', + 'shipping_phone' : shippingContact.phoneNumber || '000000000000', + 'shipping_email' : shippingContact.emailAddress || '', + 'order_comments' : '', + 'payment_method' : 'mollie_wc_gateway_applepay', + '_wp_http_referer' : '/?wc-ajax=update_order_review' + }, + complete: (jqXHR, textStatus) => { + }, + success: (authorizationResult, textStatus, jqXHR) => { + let result = authorizationResult.data + + if (authorizationResult.success === true) { + redirectionUrl = result['returnUrl']; + session.completePayment(result['responseToApple']) + window.location.href = redirectionUrl + } else { + result.errors = createAppleErrors(result.errors) + session.completePayment(result) + } + }, + error: (jqXHR, textStatus, errorThrown) => { + console.warn(textStatus, errorThrown) + session.abort() + }, + }) + }*/ + } + document.querySelector('#btn-appl').addEventListener('click', (evt) => { + evt.preventDefault() + applePaySession() + }) + } }) + .catch(applepayConfigError => { + console.error(applepayConfigError) + console.error('Error while fetching Apple Pay configuration.'); + }); + }); + } + + + }) + } +)(window) + + diff --git a/modules/ppcp-applepay/services.php b/modules/ppcp-applepay/services.php index ec2409f4e..35100af3d 100644 --- a/modules/ppcp-applepay/services.php +++ b/modules/ppcp-applepay/services.php @@ -40,8 +40,9 @@ return array( }, 'applepay.payment_method' => static function ( ContainerInterface $container ): ApplepayPaymentMethod { $settings = $container->get( 'wcgateway.settings' ); + $logger = $container->get( 'woocommerce.logger.woocommerce' ); - return new ApplepayPaymentMethod( $settings ); + return new ApplepayPaymentMethod( $settings, $logger ); }, 'applepay.url' => static function ( ContainerInterface $container ): string { $path = realpath( __FILE__ ); @@ -56,21 +57,11 @@ return array( 'applepay.sdk_script_url' => static function ( ContainerInterface $container ): string { return 'https://applepay.cdn-apple.com/jsapi/v1/apple-pay-sdk.js'; }, - 'applepay.paypal_sdk_url' => static function ( ContainerInterface $container ): string { - $settings = $container->get( 'wcgateway.settings' ); - if ( ! $settings->has( 'client_id' ) || ! $settings->has( 'merchant_id' ) ) { - return 'https://www.paypal.com/sdk/js?components=applepay'; - } - $client_id = $settings->get( 'client_id' ); - $shop_currency = get_woocommerce_currency(); - $merchant_id = $settings->get( 'merchant_id' ); - return 'https://www.paypal.com/sdk/js?client-id=' - . $client_id . '¤cy=' - . $shop_currency . '&merchant-id=' - . $merchant_id . '&components=applepay'; - }, 'applepay.script_url' => static function ( ContainerInterface $container ): string { - return trailingslashit( $container->get( 'applepay.url' ) ) . '/assets/js/paypal-applepay-direct.js'; + return trailingslashit( $container->get( 'applepay.url' ) ) . '/assets/js/applePayDirect.js'; + }, + 'applepay.style_url' => static function ( ContainerInterface $container ): string { + return trailingslashit( $container->get( 'applepay.url' ) ) . '/assets/css/applepaydirect.css'; }, 'applepay.setting_button_enabled_product' => static function ( ContainerInterface $container ): bool { $settings = $container->get( 'wcgateway.settings' ); @@ -88,4 +79,7 @@ return array( (bool) $settings->get( 'applepay_button_enabled_cart' ) : false; }, + 'applepay.data_to_scripts' => static function ( ContainerInterface $container ): DataToAppleButtonScripts { + return new DataToAppleButtonScripts(); + }, ); diff --git a/modules/ppcp-applepay/src/ApplepayModule.php b/modules/ppcp-applepay/src/ApplepayModule.php index 3c31cad96..c743ed3ff 100644 --- a/modules/ppcp-applepay/src/ApplepayModule.php +++ b/modules/ppcp-applepay/src/ApplepayModule.php @@ -84,7 +84,7 @@ class ApplepayModule implements ModuleInterface { $this->load_domain_association_file( $is_sandobx ); $this->render_buttons( $c ); - $apple_payment_method->bootstrapAjaxRequest(); + $apple_payment_method->bootstrap_ajax_request(); } /** @@ -123,7 +123,7 @@ class ApplepayModule implements ModuleInterface { */ public function load_assets(ContainerInterface $c ): void { add_action( - 'init', + 'wp', function () use ( $c ) { wp_register_script( 'wc-ppcp-applepay-sdk', @@ -133,26 +133,26 @@ class ApplepayModule implements ModuleInterface { true ); wp_enqueue_script( 'wc-ppcp-applepay-sdk' ); - wp_register_script( - 'wc-ppcp-paypal-sdk', - $c->get( 'applepay.paypal_sdk_url' ), - array(), - $c->get( 'ppcp.asset-version' ), - true - ); - wp_enqueue_script( 'wc-ppcp-paypal-sdk' ); wp_register_script( 'wc-ppcp-applepay', $c->get( 'applepay.script_url' ), - array( 'wc-ppcp-applepay-sdk', 'wc-ppcp-paypal-sdk' ), + array( 'wc-ppcp-applepay-sdk' ), $c->get( 'ppcp.asset-version' ), true ); + wp_register_style( + 'wc-ppcp-applepay', + $c->get( 'applepay.style_url' ), + array(), + $c->get( 'ppcp.asset-version' ) + ); + wp_enqueue_style( 'wc-ppcp-applepay'); wp_enqueue_script( 'wc-ppcp-applepay' ); + $data = $c->get( 'applepay.data_to_scripts' )->applePayScriptData(); wp_localize_script( 'wc-ppcp-applepay', 'wc_ppcp_applepay', - array() + $data ); } ); @@ -164,7 +164,7 @@ class ApplepayModule implements ModuleInterface { protected function apple_pay_direct_button(): void { ?>
-
+
@@ -180,6 +180,10 @@ class ApplepayModule implements ModuleInterface { public function render_buttons(ContainerInterface $c ): void { $button_enabled_product = $c->get( 'applepay.setting_button_enabled_product' ); $button_enabled_cart = $c->get( 'applepay.setting_button_enabled_cart' ); + add_filter('woocommerce_paypal_payments_sdk_components_hook', function($components) { + $components[] = 'applepay'; + return $components; + }); if ( $button_enabled_product ) { $render_placeholder = apply_filters( 'woocommerce_paypal_payments_applepay_render_hook_product', 'woocommerce_after_add_to_cart_form' ); $render_placeholder = is_string( $render_placeholder ) ? $render_placeholder : 'woocommerce_after_add_to_cart_form'; diff --git a/modules/ppcp-applepay/src/ApplepayPaymentMethod.php b/modules/ppcp-applepay/src/ApplepayPaymentMethod.php index eb12b052b..cba1a812c 100644 --- a/modules/ppcp-applepay/src/ApplepayPaymentMethod.php +++ b/modules/ppcp-applepay/src/ApplepayPaymentMethod.php @@ -9,6 +9,7 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Applepay; +use Psr\Log\LoggerInterface; use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; @@ -23,6 +24,17 @@ class ApplepayPaymentMethod { * @var Settings */ private $plugin_settings; + /** + * The logger. + * + * @var LoggerInterface + */ + private $logger; + /** + * @var ResponsesToApple + */ + private $response_templates; + private $old_cart_contents; /** * PayPalPaymentMethod constructor. @@ -30,9 +42,12 @@ class ApplepayPaymentMethod { * @param Settings $plugin_settings The settings. */ public function __construct( - Settings $plugin_settings + Settings $plugin_settings, + LoggerInterface $logger ) { $this->plugin_settings = $plugin_settings; + $this->response_templates = new ResponsesToApple(); + $this->logger = $logger; } /** @@ -58,6 +73,23 @@ class ApplepayPaymentMethod { $data['products'][0] = 'PAYMENT_METHODS'; } $data['capabilities'][] = 'APPLE_PAY'; + $nonce = $data['operations'][0]['api_integration_preference']['rest_api_integration']['first_party_details']['seller_nonce']; + $data['operations'][] = array( + 'operation' => 'API_INTEGRATION', + 'api_integration_preference' => array( + 'rest_api_integration' => array( + 'integration_method' => 'PAYPAL', + 'integration_type' => 'THIRD_PARTY', + 'third_party_details' => array( + 'features' => array( + 'PAYMENT', + 'REFUND', + ), + 'seller_nonce' => $nonce + ), + ), + ), + ); return $data; } @@ -92,14 +124,6 @@ class ApplepayPaymentMethod { * Adds all the Ajax actions to perform the whole workflow */ public function bootstrap_ajax_request(): void { - add_action( - 'wp_ajax_' . PropertiesDictionary::VALIDATION, - array( $this, 'validate_merchant' ) - ); - add_action( - 'wp_ajax_nopriv_' . PropertiesDictionary::VALIDATION, - array( $this, 'validate_merchant' ) - ); add_action( 'wp_ajax_' . PropertiesDictionary::CREATE_ORDER, array( $this, 'create_wc_order' ) @@ -133,16 +157,6 @@ class ApplepayPaymentMethod { array( $this, 'update_shipping_method' ) ); } - /** - * Method to validate the merchant against Apple system - * On fail triggers and option that shows an admin notice showing the error - * On success returns the validation data to the script - */ - public function validate_merchant(): void { - // TODO validate merchant. - - // $this->plugin_settings->set('applepay_validated', 'yes'); - } /** * Method to validate and update the shipping contact of the user @@ -151,6 +165,51 @@ class ApplepayPaymentMethod { * On success returns the new contact data */ public function update_shipping_contact(): void { + $applepay_request_data_object = $this->applepay_data_object_http(); + if (!$this->is_nonce_valid()) { + return; + } + $applepay_request_data_object->update_contact_data(); + if ($applepay_request_data_object->has_errors()) { + $this->response_templates->response_with_data_errors($applepay_request_data_object->errors()); + return; + } + + if (!class_exists('WC_Countries')) { + return; + } + + $countries = $this->create_wc_countries(); + $allowed_selling_countries = $countries->get_allowed_countries(); + $allowed_shipping_countries = $countries->get_shipping_countries(); + $user_country = $applepay_request_data_object->simplified_contact()['country']; + $is_allowed_selling_country = array_key_exists( + $user_country, + $allowed_selling_countries + ); + + $is_allowed_shipping_country = array_key_exists( + $user_country, + $allowed_shipping_countries + ); + $product_need_shipping = $applepay_request_data_object->need_shipping(); + + if (!$is_allowed_selling_country) { + $this->response_templates->response_with_data_errors( + [['errorCode' => 'addressUnserviceable']] + ); + return; + } + if ($product_need_shipping && !$is_allowed_shipping_country) { + $this->response_templates->response_with_data_errors( + [['errorCode' => 'addressUnserviceable']] + ); + return; + } + + $payment_details = $this->which_calculate_totals($applepay_request_data_object); + $response = $this->response_templates->apple_formatted_response($payment_details); + $this->response_templates->response_success($response); } /** @@ -160,6 +219,17 @@ class ApplepayPaymentMethod { * On success returns the new contact data */ public function update_shipping_method(): void { + $applepay_request_data_object = $this->applepay_data_object_http(); + if (!$this->is_nonce_valid()) { + return; + } + $applepay_request_data_object->update_method_data(); + if ($applepay_request_data_object->has_errors()) { + $this->response_templates->response_with_data_errors($applepay_request_data_object->errors()); + } + $paymentDetails = $this->which_calculate_totals($applepay_request_data_object); + $response = $this->response_templates->apple_formatted_response($paymentDetails); + $this->response_templates->response_success($response); } /** @@ -192,6 +262,393 @@ class ApplepayPaymentMethod { return wp_verify_nonce( $nonce, 'woocommerce-process_checkout' + ) === 1; + } + + /** + * Data Object to collect and validate all needed data collected + * through HTTP + */ + protected function applepay_data_object_http(): ApplePayDataObjectHttp + { + return new ApplePayDataObjectHttp($this->logger); + } + + /** + * Returns a WC_Countries instance to check shipping + * + * @return \WC_Countries + */ + protected function create_wc_countries() + { + return new \WC_Countries(); + } + + /** + * Selector between product detail and cart page calculations + * + * @param $applepay_request_data_object + * + * @return array|bool + */ + protected function which_calculate_totals( + $applepay_request_data_object + ) { + + if ($applepay_request_data_object->caller_page === 'productDetail') { + return $this->calculate_totals_single_product( + $applepay_request_data_object->product_id(), + $applepay_request_data_object->product_quantity(), + $applepay_request_data_object->simplified_contact(), + $applepay_request_data_object->shipping_method() + ); + } + if ($applepay_request_data_object->caller_page === 'cart') { + return $this->calculate_totals_cart_page( + $applepay_request_data_object->simplified_contact(), + $applepay_request_data_object->shipping_method() + ); + } + return false; + } + + /** + * Calculates totals for the product with the given information + * Saves the previous cart to reload it after calculations + * If no shippingMethodId provided will return the first available shipping + * method + * + * @param $product_id + * @param $product_quantity + * @param $customer_address + * @param null $shipping_method + */ + protected function calculate_totals_single_product( + $product_id, + $product_quantity, + $customer_address, + $shipping_method = null + ): array { + + $results = []; + $reload_cart = false; + if (!WC()->cart->is_empty()) { + $old_cart_contents = WC()->cart->get_cart_contents(); + foreach (array_keys($old_cart_contents) as $cart_item_key) { + WC()->cart->remove_cart_item($cart_item_key); + } + $reload_cart = true; + } + try { + //I just care about apple address details + $shipping_method_id = ''; + $shipping_methods_array = []; + $selected_shipping_method = []; + $this->customer_address($customer_address); + $cart = WC()->cart; + if ($shipping_method) { + $shipping_method_id = $shipping_method['identifier']; + WC()->session->set( + 'chosen_shipping_methods', + [$shipping_method_id] + ); + } + $cart_item_key = $cart->add_to_cart($product_id, $product_quantity); + if ($cart->needs_shipping()) { + list( + $shipping_methods_array, $selected_shipping_method + ) = $this->cart_shipping_methods( + $cart, + $customer_address, + $shipping_method, + $shipping_method_id + ); + } + + $cart->calculate_shipping(); + $cart->calculate_fees(); + $cart->calculate_totals(); + + $results = $this->cart_calculation_results( + $cart, + $selected_shipping_method, + $shipping_methods_array + ); + + $cart->remove_cart_item($cart_item_key); + $this->customer_address(); + if ($reload_cart) { + foreach (array_keys($old_cart_contents) as $cart_item_key) { + $cart->restore_cart_item($cart_item_key); + } + } + } catch (Exception $exception) { + } + return $results; + } + + /** + * Sets the customer address with ApplePay details to perform correct + * calculations + * If no parameter passed then it resets the customer to shop details + */ + protected function customer_address(array $address = []) + { + $base_location = wc_get_base_location(); + $shop_country_code = $base_location['country']; + WC()->customer->set_shipping_country( + $address['country'] ?? $shop_country_code + ); + WC()->customer->set_billing_country( + $address['country'] ?? $shop_country_code + ); + WC()->customer->set_shipping_postcode( + $address['postcode'] ?? $shop_country_code + ); + WC()->customer->set_shipping_city( + $address['city'] ?? $shop_country_code + ); + } + + /** + * Add shipping methods to cart to perform correct calculations + * + * @param $cart + * @param $customer_address + * @param $shipping_method + * @param $shipping_method_id + */ + protected function cart_shipping_methods( + $cart, + $customer_address, + $shipping_method, + $shipping_method_id + ): array { + + $shipping_methods_array = []; + $shipping_methods = WC()->shipping->calculate_shipping( + $this->getShippingPackages( + $customer_address, + $cart->get_total('edit') + ) + ); + $done = false; + foreach ($shipping_methods[0]['rates'] as $rate) { + $shipping_methods_array[] = [ + "label" => $rate->get_label(), + "detail" => "", + "amount" => $rate->get_cost(), + "identifier" => $rate->get_id(), + ]; + if (!$done) { + $done = true; + $shipping_method_id = $shipping_method ? $shipping_method_id + : $rate->get_id(); + WC()->session->set( + 'chosen_shipping_methods', + [$shipping_method_id] + ); + } + } + + $selected_shipping_method = $shipping_methods_array[0]; + if ($shipping_method) { + $selected_shipping_method = $shipping_method; + } + + return [$shipping_methods_array, $selected_shipping_method]; + } + + /** + * Sets shipping packages for correct calculations + * @param $customer_address + * @param $total + * + * @return mixed|void|null + */ + protected function getShippingPackages($customer_address, $total) + { + // Packages array for storing 'carts' + $packages = []; + $packages[0]['contents'] = WC()->cart->cart_contents; + $packages[0]['contents_cost'] = $total; + $packages[0]['applied_coupons'] = WC()->session->applied_coupon; + $packages[0]['destination']['country'] = $customer_address['country']; + $packages[0]['destination']['state'] = ''; + $packages[0]['destination']['postcode'] = $customer_address['postcode']; + $packages[0]['destination']['city'] = $customer_address['city']; + $packages[0]['destination']['address'] = ''; + $packages[0]['destination']['address_2'] = ''; + + return apply_filters('woocommerce_cart_shipping_packages', $packages); + } + + /** + * Returns the formatted results of the cart calculations + * + * @param $cart + * @param $selected_shipping_method + * @param $shipping_methods_array + */ + protected function cart_calculation_results( + $cart, + $selected_shipping_method, + $shipping_methods_array + ): array { + $total = $cart->get_total('edit'); + $total = round($total, 2); + return [ + 'subtotal' => $cart->get_subtotal(), + 'shipping' => [ + 'amount' => $cart->needs_shipping() + ? $cart->get_shipping_total() : null, + 'label' => $cart->needs_shipping() + ? $selected_shipping_method['label'] : null, + ], + + 'shippingMethods' => $cart->needs_shipping() + ? $shipping_methods_array : null, + 'taxes' => $cart->get_total_tax(), + 'total' => $total, + ]; + } + + /** + * Calculates totals for the cart page with the given information + * If no shippingMethodId provided will return the first available shipping + * method + * + * @param $customer_address + * @param null $shipping_method + */ + protected function calculate_totals_cart_page( + $customer_address = null, + $shipping_method = null + ): array { + + $results = []; + if (WC()->cart->is_empty()) { + return []; + } + try { + $shipping_methods_array = []; + $selected_shipping_method = []; + //I just care about apple address details + $this->customer_address($customer_address); + $cart = WC()->cart; + if ($shipping_method) { + WC()->session->set( + 'chosen_shipping_methods', + [$shipping_method['identifier']] + ); + } + + if ($cart->needs_shipping()) { + list( + $shipping_methods_array, $selected_shipping_method + ) = $this->cart_shipping_methods( + $cart, + $customer_address, + $shipping_method, + $shipping_method['identifier'] + ); + } + $cart->calculate_shipping(); + $cart->calculate_fees(); + $cart->calculate_totals(); + + $results = $this->cart_calculation_results( + $cart, + $selected_shipping_method, + $shipping_methods_array + ); + + $this->customer_address(); + } catch (Exception $e) { + } + + return $results; + } + + /** + * Add address billing and shipping data to order + * + * @param ApplePayDataObjectHttp $applepay_request_data_object + * @param $order + * + */ + protected function addAddressesToOrder( + ApplePayDataObjectHttp $applepay_request_data_object + ) { + + add_action( + 'woocommerce_checkout_create_order', + static function ($order, $data) use ($applepay_request_data_object) { + if ($applepay_request_data_object->shipping_method() !== null) { + $billing_address = $applepay_request_data_object->billing_address(); + $shipping_address = $applepay_request_data_object->shipping_address(); + //apple puts email in shipping_address while we get it from WC's billing_address + $billing_address['email'] = $shipping_address['email']; + $billing_address['phone'] = $shipping_address['phone']; + + $order->set_address($billing_address, 'billing'); + $order->set_address($shipping_address, 'shipping'); + } + }, + 10, + 2 + ); + } + /** + * Empty the cart to use for calculations + * while saving its contents in a field + */ + protected function empty_current_cart() + { + foreach ($this->old_cart_contents as $cart_item_key => $value) { + WC()->cart->remove_cart_item($cart_item_key); + } + $this->reload_cart = true; + } + + /** + * @param WC_Cart $cart + */ + protected function reload_cart(WC_Cart $cart): void + { + foreach ($this->old_cart_contents as $cart_item_key => $value) { + $cart->restore_cart_item($cart_item_key); + } + } + + protected function response_after_successful_result(): void + { + add_filter( + 'woocommerce_payment_successful_result', + function ($result, $order_id) { + if ( + isset($result['result']) + && 'success' === $result['result'] + ) { + $this->response_templates->response_success( + $this->response_templates->authorization_result_response( + 'STATUS_SUCCESS', + $order_id + ) + ); + } else { + wp_send_json_error( + $this->response_templates->authorization_result_response( + 'STATUS_FAILURE', + 0, + [['errorCode' => 'unknown']] + ) + ); + } + return $result; + }, + 10, + 2 ); } } diff --git a/modules/ppcp-applepay/src/DataToAppleButtonScripts.php b/modules/ppcp-applepay/src/DataToAppleButtonScripts.php new file mode 100644 index 000000000..6ff97008f --- /dev/null +++ b/modules/ppcp-applepay/src/DataToAppleButtonScripts.php @@ -0,0 +1,138 @@ +dataForProductPage( + $shopCountryCode, + $currencyCode, + $totalLabel + ); + } + if ( is_cart() || $isBlock ) { + return $this->dataForCartPage( + $shopCountryCode, + $currencyCode, + $totalLabel + ); + } + return array(); + } + + /** + * Check if the product needs shipping + * + * @param $product + * + * @return bool + */ + protected function checkIfNeedShipping( $product ) { + if ( + ! wc_shipping_enabled() + || 0 === wc_get_shipping_method_count( + true + ) + ) { + return false; + } + $needs_shipping = false; + + if ( $product->needs_shipping() ) { + $needs_shipping = true; + } + + return $needs_shipping; + } + + /** + * @param $shopCountryCode + * @param $currencyCode + * @param $totalLabel + * + * @return array + */ + protected function dataForProductPage( + $shopCountryCode, + $currencyCode, + $totalLabel + ) { + + $product = wc_get_product( get_the_id() ); + if ( ! $product ) { + return array(); + } + $isVariation = false; + if ( $product->get_type() === 'variable' || $product->get_type() === 'variable-subscription' ) { + $isVariation = true; + } + $productNeedShipping = $this->checkIfNeedShipping( $product ); + $productId = get_the_id(); + $productPrice = $product->get_price(); + $productStock = $product->get_stock_status(); + + return array( + 'product' => array( + 'needShipping' => $productNeedShipping, + 'id' => $productId, + 'price' => $productPrice, + 'isVariation' => $isVariation, + 'stock' => $productStock, + ), + 'shop' => array( + 'countryCode' => $shopCountryCode, + 'currencyCode' => $currencyCode, + 'totalLabel' => $totalLabel, + ), + 'ajaxUrl' => admin_url( 'admin-ajax.php' ), + ); + } + + /** + * @param $shopCountryCode + * @param $currencyCode + * @param $totalLabel + * + * @return array + */ + protected function dataForCartPage( + $shopCountryCode, + $currencyCode, + $totalLabel + ) { + + $cart = WC()->cart; + $nonce = wp_nonce_field( 'woocommerce-process_checkout', 'woocommerce-process-checkout-nonce' ); + $buttonMarkup = + '
' + . $nonce + . '
'; + return array( + 'product' => array( + 'needShipping' => $cart->needs_shipping(), + 'subtotal' => $cart->get_subtotal(), + ), + 'shop' => array( + 'countryCode' => $shopCountryCode, + 'currencyCode' => $currencyCode, + 'totalLabel' => $totalLabel, + ), + 'ajaxUrl' => admin_url( 'admin-ajax.php' ), + 'buttonMarkup' => $buttonMarkup, + ); + } +} diff --git a/modules/ppcp-applepay/src/PropertiesDictionary.php b/modules/ppcp-applepay/src/PropertiesDictionary.php index 94e2f7a4d..abeef3675 100644 --- a/modules/ppcp-applepay/src/PropertiesDictionary.php +++ b/modules/ppcp-applepay/src/PropertiesDictionary.php @@ -14,13 +14,6 @@ namespace WooCommerce\PayPalCommerce\Applepay; */ class PropertiesDictionary { - - public const VALIDATION_REQUIRED_FIELDS = - array( - self::WCNONCE, - self::VALIDATION_URL, - ); - public const BILLING_CONTACT_INVALID = 'billing Contact Invalid'; public const CREATE_ORDER_SINGLE_PROD_REQUIRED_FIELDS = @@ -58,8 +51,6 @@ class PropertiesDictionary { self::NEED_SHIPPING, ); - public const VALIDATION_URL = 'validationUrl'; - public const UPDATE_METHOD_SINGLE_PROD_REQUIRED_FIELDS = array( self::WCNONCE, @@ -70,13 +61,13 @@ class PropertiesDictionary { self::SIMPLIFIED_CONTACT, ); - public const PRODUCT_ID = 'productId'; + public const PRODUCT_ID = 'product_id'; - public const SIMPLIFIED_CONTACT = 'simplifiedContact'; + public const SIMPLIFIED_CONTACT = 'simplified_contact'; - public const SHIPPING_METHOD = 'shippingMethod'; + public const SHIPPING_METHOD = 'shipping_method'; - public const SHIPPING_CONTACT = 'shippingContact'; + public const SHIPPING_CONTACT = 'shipping_contact'; public const SHIPPING_CONTACT_INVALID = 'shipping Contact Invalid'; @@ -91,23 +82,21 @@ class PropertiesDictionary { self::SHIPPING_CONTACT, ); - public const PRODUCT_QUANTITY = 'productQuantity'; + public const PRODUCT_QUANTITY = 'product_quantity'; - public const CALLER_PAGE = 'callerPage'; + public const CALLER_PAGE = 'caller_page'; - public const BILLING_CONTACT = 'billingContact'; + public const BILLING_CONTACT = 'billing_contact'; - public const NEED_SHIPPING = 'needShipping'; + public const NEED_SHIPPING = 'need_shipping'; - public const UPDATE_SHIPPING_CONTACT = 'woocommerce_paypal_payments_update_shipping_contact'; + public const UPDATE_SHIPPING_CONTACT = 'ppcp_update_shipping_contact'; - public const UPDATE_SHIPPING_METHOD = 'woocommerce_paypal_payments_update_shipping_method'; + public const UPDATE_SHIPPING_METHOD = 'ppcp_update_shipping_method'; - public const VALIDATION = 'woocommerce_paypal_payments_validation'; + public const CREATE_ORDER = 'ppcp_create_order'; - public const CREATE_ORDER = 'woocommerce_paypal_payments_create_order'; + public const CREATE_ORDER_CART = 'ppcp_create_order_cart'; - public const CREATE_ORDER_CART = 'woocommerce_paypal_payments_create_order_cart'; - - public const REDIRECT = 'woocommerce_paypal_payments_redirect'; + public const REDIRECT = 'ppcp_redirect'; } diff --git a/modules/ppcp-applepay/yarn.lock b/modules/ppcp-applepay/yarn.lock index 32eb30c2e..8720b8b62 100644 --- a/modules/ppcp-applepay/yarn.lock +++ b/modules/ppcp-applepay/yarn.lock @@ -963,7 +963,7 @@ resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310" integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== -"@babel/runtime@^7.8.4": +"@babel/runtime@^7.13.10", "@babel/runtime@^7.16.0", "@babel/runtime@^7.8.4": version "7.22.6" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.6.tgz#57d64b9ae3cff1d67eb067ae117dac087f5bd438" integrity sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ== @@ -1090,6 +1090,32 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-20.3.3.tgz#329842940042d2b280897150e023e604d11657d6" integrity sha512-wheIYdr4NYML61AjC8MKj/2jrR/kDQri/CIpVoZwldwhnIrD/j9jIU5bJ8yBKuB2VhpFV7Ab6G2XkBjv9r9Zzw== +"@types/prop-types@*": + version "15.7.5" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf" + integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w== + +"@types/react-dom@^16.9.0": + version "16.9.19" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.19.tgz#6a139c26b02dec533a7fa131f084561babb10a8f" + integrity sha512-xC8D280Bf6p0zguJ8g62jcEOKZiUbx9sIe6O3tT/lKfR87A7A6g65q13z6D5QUMIa/6yFPkNhqjF5z/VVZEYqQ== + dependencies: + "@types/react" "^16" + +"@types/react@^16", "@types/react@^16.9.0": + version "16.14.43" + resolved "https://registry.yarnpkg.com/@types/react/-/react-16.14.43.tgz#bc6e7a0e99826809591d38ddf1193955de32c446" + integrity sha512-7zdjv7jvoLLQg1tTvpQsm+hyNUMT2mPlNV1+d0I8fbGhkJl82spopMyBlu4wb1dviZAxpGdk5eHu/muacknnfw== + dependencies: + "@types/prop-types" "*" + "@types/scheduler" "*" + csstype "^3.0.2" + +"@types/scheduler@*": + version "0.16.3" + resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.3.tgz#cef09e3ec9af1d63d2a6cc5b383a737e24e6dcf5" + integrity sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ== + "@webassemblyjs/ast@1.11.6", "@webassemblyjs/ast@^1.11.5": version "1.11.6" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.6.tgz#db046555d3c413f8966ca50a95176a0e2c642e24" @@ -1243,6 +1269,26 @@ json2php "^0.0.4" webpack-sources "^3.2.2" +"@wordpress/element@^3.0.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@wordpress/element/-/element-3.2.0.tgz#531720b3a023a1509f13d2464b2e1d670153116e" + integrity sha512-YXJhtBF8FnFYwA9X6Dvs4k6yJf5wy1lhU04VNJVzoUDwCt/pK747RGePIPDdUWVd3X/TlyNH2yLRtcCyOC/SzQ== + dependencies: + "@babel/runtime" "^7.13.10" + "@types/react" "^16.9.0" + "@types/react-dom" "^16.9.0" + "@wordpress/escape-html" "^2.2.0" + lodash "^4.17.21" + react "^17.0.1" + react-dom "^17.0.1" + +"@wordpress/escape-html@^2.2.0": + version "2.38.0" + resolved "https://registry.yarnpkg.com/@wordpress/escape-html/-/escape-html-2.38.0.tgz#72dd11ba66e26498b4379a4d8b7d020ba26d879b" + integrity sha512-q7wg1JvXVPpyddMnEl6A8ALn9U3mA4LvyQpkDNLonntU+Q8JbvW1r91HdzoFh396rHoNJWGzDGORUTlDlb5jOw== + dependencies: + "@babel/runtime" "^7.16.0" + "@xtuc/ieee754@^1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" @@ -1467,6 +1513,11 @@ cross-spawn@^7.0.1, cross-spawn@^7.0.3: shebang-command "^2.0.0" which "^2.0.1" +csstype@^3.0.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b" + integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ== + debug@^4.1.0, debug@^4.1.1: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" @@ -1723,7 +1774,7 @@ jest-worker@^27.4.5: merge-stream "^2.0.0" supports-color "^8.0.0" -js-tokens@^4.0.0: +"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== @@ -1794,6 +1845,18 @@ lodash.debounce@^4.0.8: resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== +lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +loose-envify@^1.1.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" @@ -1845,6 +1908,11 @@ normalize-path@^3.0.0, normalize-path@~3.0.0: resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== +object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== + p-limit@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" @@ -1908,6 +1976,23 @@ randombytes@^2.1.0: dependencies: safe-buffer "^5.1.0" +react-dom@^17.0.1: + version "17.0.2" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23" + integrity sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + scheduler "^0.20.2" + +react@^17.0.1: + version "17.0.2" + resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037" + integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + readdirp@~3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" @@ -2008,6 +2093,14 @@ sass@^1.42.1: immutable "^4.0.0" source-map-js ">=0.6.2 <2.0.0" +scheduler@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.2.tgz#4baee39436e34aa93b4874bddcbf0fe8b8b50e91" + integrity sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + schema-utils@^2.6.5: version "2.7.1" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.1.tgz#1ca4f32d1b24c590c203b8e7a50bf0ea4cd394d7" diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index b40bfd266..22074ddcc 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -1162,7 +1162,8 @@ class SmartButton implements SmartButtonInterface { if ( $this->dcc_is_enabled() ) { $components[] = 'hosted-fields'; } - return $components; + //return filterable array of components + return apply_filters( 'woocommerce_paypal_payments_sdk_components_hook', $components ); } /**