diff --git a/modules/ppcp-api-client/src/ApiModule.php b/modules/ppcp-api-client/src/ApiModule.php index ed09ecddc..42bc1f117 100644 --- a/modules/ppcp-api-client/src/ApiModule.php +++ b/modules/ppcp-api-client/src/ApiModule.php @@ -48,8 +48,8 @@ class ApiModule implements ModuleInterface { 'ppcp_create_order_request_body_data', function( array $data ) use ( $c ) { - foreach ( $data['purchase_units'] as $purchase_unit_index => $purchase_unit ) { - foreach ( $purchase_unit['items'] as $item_index => $item ) { + foreach ( ( $data['purchase_units'] ?? array() ) as $purchase_unit_index => $purchase_unit ) { + foreach ( ( $purchase_unit['items'] ?? array() ) as $item_index => $item ) { $data['purchase_units'][ $purchase_unit_index ]['items'][ $item_index ]['name'] = apply_filters( 'woocommerce_paypal_payments_cart_line_item_name', $item['name'], $item['cart_item_key'] ?? null ); } diff --git a/modules/ppcp-api-client/src/Helper/OrderTransient.php b/modules/ppcp-api-client/src/Helper/OrderTransient.php index b6b7a0d99..efe980928 100644 --- a/modules/ppcp-api-client/src/Helper/OrderTransient.php +++ b/modules/ppcp-api-client/src/Helper/OrderTransient.php @@ -102,7 +102,7 @@ class OrderTransient { $transient = array(); } - if ( ! is_array( $transient['notes'] ) ) { + if ( ! is_array( $transient['notes'] ?? null ) ) { $transient['notes'] = array(); } diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index 6cab7d5ca..8c35c9eb5 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -26,7 +26,7 @@ class ApplepayButton { this.updated_contact_info = [] this.selectedShippingMethod = [] - this.nonce = document.getElementById('woocommerce-process-checkout-nonce')?.value + this.nonce = document.getElementById('woocommerce-process-checkout-nonce')?.value || buttonConfig.nonce this.log = function() { if ( this.buttonConfig.is_debug ) { @@ -243,6 +243,11 @@ class ApplepayButton { requiredShippingContactFields: ["postalAddress", "email", "phone"], requiredBillingContactFields: ["postalAddress", "email", "phone"], } + + if (!this.contextHandler.shippingAllowed()) { + baseRequest.requiredShippingContactFields = []; + } + const paymentDataRequest = Object.assign({}, baseRequest); paymentDataRequest.currencyCode = buttonConfig.shop.currencyCode; paymentDataRequest.total = { @@ -512,18 +517,53 @@ class ApplepayButton { if (confirmOrderResponse && confirmOrderResponse.approveApplePayPayment) { if (confirmOrderResponse.approveApplePayPayment.status === "APPROVED") { try { - let data = { - billing_contact: event.payment.billingContact, - shipping_contact: event.payment.shippingContact, - paypal_order_id: id, - }; - let authorizationResult = await processInWooAndCapture(data); - if (authorizationResult.result === "success") { - session.completePayment(ApplePaySession.STATUS_SUCCESS) - window.location.href = authorizationResult.redirect + + if (!this.contextHandler.shippingAllowed()) { + // No shipping, expect immediate capture, ex: PayNow. + + let approveFailed = false; + await this.contextHandler.approveOrder({ + orderID: id + }, { // actions mock object. + restart: () => new Promise((resolve, reject) => { + approveFailed = true; + resolve(); + }), + order: { + get: () => new Promise((resolve, reject) => { + resolve(null); + }) + } + }); + + if (!approveFailed) { + this.log('onpaymentauthorized approveOrder OK'); + session.completePayment(ApplePaySession.STATUS_SUCCESS); + } else { + this.log('onpaymentauthorized approveOrder FAIL'); + session.completePayment(ApplePaySession.STATUS_FAILURE); + session.abort() + console.error(error); + } + } else { - session.completePayment(ApplePaySession.STATUS_FAILURE) + // Default payment. + + let data = { + billing_contact: event.payment.billingContact, + shipping_contact: event.payment.shippingContact, + paypal_order_id: id, + }; + let authorizationResult = await processInWooAndCapture(data); + if (authorizationResult.result === "success") { + session.completePayment(ApplePaySession.STATUS_SUCCESS) + window.location.href = authorizationResult.redirect + } else { + session.completePayment(ApplePaySession.STATUS_FAILURE) + } + } + } catch (error) { session.completePayment(ApplePaySession.STATUS_FAILURE); session.abort() diff --git a/modules/ppcp-applepay/resources/js/Context/BaseHandler.js b/modules/ppcp-applepay/resources/js/Context/BaseHandler.js index dea7de698..3deb4a47e 100644 --- a/modules/ppcp-applepay/resources/js/Context/BaseHandler.js +++ b/modules/ppcp-applepay/resources/js/Context/BaseHandler.js @@ -1,8 +1,6 @@ import ErrorHandler from "../../../../ppcp-button/resources/js/modules/ErrorHandler"; import CartActionHandler from "../../../../ppcp-button/resources/js/modules/ActionHandler/CartActionHandler"; -import onApprove - from "../../../../ppcp-button/resources/js/modules/OnApproveHandler/onApproveForContinue"; class BaseHandler { @@ -11,6 +9,10 @@ class BaseHandler { this.ppcpConfig = ppcpConfig; } + shippingAllowed() { + return true; + } + transactionInfo() { return new Promise((resolve, reject) => { @@ -42,30 +44,32 @@ class BaseHandler { } createOrder() { - const errorHandler = new ErrorHandler( - this.ppcpConfig.labels.error.generic, - document.querySelector('.woocommerce-notices-wrapper') - ); - - const actionHandler = new CartActionHandler( - this.ppcpConfig, - errorHandler, - ); - - return actionHandler.configuration().createOrder(null, null); + return this.actionHandler().configuration().createOrder(null, null); } - approveOrderForContinue(data, actions) { - const errorHandler = new ErrorHandler( + approveOrder(data, actions) { + return this.actionHandler().configuration().onApprove(data, actions); + } + + actionHandler() { + return new CartActionHandler( + this.ppcpConfig, + this.errorHandler(), + ); + } + + errorHandler() { + return new ErrorHandler( this.ppcpConfig.labels.error.generic, document.querySelector('.woocommerce-notices-wrapper') ); + } - let onApproveHandler = onApprove({ - config: this.ppcpConfig - }, errorHandler); - - return onApproveHandler(data, actions); + errorHandler() { + return new ErrorHandler( + this.ppcpConfig.labels.error.generic, + document.querySelector('.woocommerce-notices-wrapper') + ); } } diff --git a/modules/ppcp-applepay/resources/js/Context/ContextHandlerFactory.js b/modules/ppcp-applepay/resources/js/Context/ContextHandlerFactory.js index d94833daa..bea8e837b 100644 --- a/modules/ppcp-applepay/resources/js/Context/ContextHandlerFactory.js +++ b/modules/ppcp-applepay/resources/js/Context/ContextHandlerFactory.js @@ -5,6 +5,7 @@ import CartBlockHandler from "./CartBlockHandler"; import CheckoutBlockHandler from "./CheckoutBlockHandler"; import MiniCartHandler from "./MiniCartHandler"; import PreviewHandler from "./PreviewHandler"; +import PayNowHandler from "./PayNowHandler"; class ContextHandlerFactory { @@ -15,8 +16,9 @@ class ContextHandlerFactory { case 'cart': return new CartHandler(buttonConfig, ppcpConfig); case 'checkout': - case 'pay-now': // same as checkout return new CheckoutHandler(buttonConfig, ppcpConfig); + case 'pay-now': + return new PayNowHandler(buttonConfig, ppcpConfig); case 'mini-cart': return new MiniCartHandler(buttonConfig, ppcpConfig); case 'cart-block': diff --git a/modules/ppcp-applepay/resources/js/Context/PayNowHandler.js b/modules/ppcp-applepay/resources/js/Context/PayNowHandler.js new file mode 100644 index 000000000..add275608 --- /dev/null +++ b/modules/ppcp-applepay/resources/js/Context/PayNowHandler.js @@ -0,0 +1,35 @@ +import Spinner from "../../../../ppcp-button/resources/js/modules/Helper/Spinner"; +import BaseHandler from "./BaseHandler"; +import CheckoutActionHandler + from "../../../../ppcp-button/resources/js/modules/ActionHandler/CheckoutActionHandler"; + +class PayNowHandler extends BaseHandler { + + shippingAllowed() { + return false; + } + + transactionInfo() { + return new Promise(async (resolve, reject) => { + const data = this.ppcpConfig['pay_now']; + + resolve({ + countryCode: data.country_code, + currencyCode: data.currency_code, + totalPriceStatus: 'FINAL', + totalPrice: data.total_str + }); + }); + } + + actionHandler() { + return new CheckoutActionHandler( + this.ppcpConfig, + this.errorHandler(), + new Spinner() + ); + } + +} + +export default PayNowHandler; diff --git a/modules/ppcp-applepay/resources/js/Context/SingleProductHandler.js b/modules/ppcp-applepay/resources/js/Context/SingleProductHandler.js index f9adb7774..c5d0549d2 100644 --- a/modules/ppcp-applepay/resources/js/Context/SingleProductHandler.js +++ b/modules/ppcp-applepay/resources/js/Context/SingleProductHandler.js @@ -49,19 +49,14 @@ class SingleProductHandler extends BaseHandler { } createOrder() { - return this.actionHandler().configuration().createOrder(); - } - - products() { - return this.actionHandler().getProducts(); + return this.actionHandler().configuration().createOrder(null, null, { + 'updateCartOptions': { + 'keepShipping': true + } + }); } actionHandler() { - const errorHandler = new ErrorHandler( - this.ppcpConfig.labels.error.generic, - document.querySelector('.woocommerce-notices-wrapper') - ); - return new SingleProductActionHandler( this.ppcpConfig, new UpdateCart( @@ -69,10 +64,13 @@ class SingleProductHandler extends BaseHandler { this.ppcpConfig.ajax.change_cart.nonce, ), document.querySelector('form.cart'), - errorHandler, + this.errorHandler(), ); } + products() { + return this.actionHandler().getProducts(); + } } export default SingleProductHandler; diff --git a/modules/ppcp-applepay/services.php b/modules/ppcp-applepay/services.php index a49ab7fad..f345409e6 100644 --- a/modules/ppcp-applepay/services.php +++ b/modules/ppcp-applepay/services.php @@ -130,6 +130,24 @@ return array( return apply_filters( 'woocommerce_paypal_payments_applepay_supported_country_currency_matrix', array( + 'CA' => array( + 'AUD', + 'CAD', + 'CHF', + 'CZK', + 'DKK', + 'EUR', + 'GBP', + 'HKD', + 'HUF', + 'JPY', + 'NOK', + 'NZD', + 'PLN', + 'SEK', + 'SGD', + 'USD', + ), 'GB' => array( 'AUD', 'CAD', @@ -148,6 +166,24 @@ return array( 'SGD', 'USD', ), + 'IT' => array( + 'AUD', + 'CAD', + 'CHF', + 'CZK', + 'DKK', + 'EUR', + 'GBP', + 'HKD', + 'HUF', + 'JPY', + 'NOK', + 'NZD', + 'PLN', + 'SEK', + 'SGD', + 'USD', + ), 'US' => array( 'AUD', 'CAD', @@ -156,24 +192,6 @@ return array( 'JPY', 'USD', ), - 'CA' => array( - 'AUD', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'JPY', - 'NOK', - 'NZD', - 'PLN', - 'SEK', - 'SGD', - 'USD', - ), ) ); }, diff --git a/modules/ppcp-applepay/src/ApplepayModule.php b/modules/ppcp-applepay/src/ApplepayModule.php index 1abbf339b..378af1ff5 100644 --- a/modules/ppcp-applepay/src/ApplepayModule.php +++ b/modules/ppcp-applepay/src/ApplepayModule.php @@ -84,14 +84,16 @@ class ApplepayModule implements ModuleInterface { return; } - $module->load_assets( $c, $apple_payment_method ); - $module->handle_validation_file( $c ); - $module->render_buttons( $c, $apple_payment_method ); + if ( $apple_payment_method->is_enabled() ) { + $module->load_assets( $c, $apple_payment_method ); + $module->handle_validation_file( $c, $apple_payment_method ); + $module->render_buttons( $c, $apple_payment_method ); + $apple_payment_method->bootstrap_ajax_request(); + } - $apple_payment_method->bootstrap_ajax_request(); - - $module->load_admin_assets( $c, $apple_payment_method ); - } + $module->load_admin_assets( $c, $apple_payment_method ); + }, + 1 ); add_filter( @@ -253,9 +255,13 @@ class ApplepayModule implements ModuleInterface { * Handles the validation file. * * @param ContainerInterface $c The container. + * @param ApplePayButton $button The button. * @return void */ - public function handle_validation_file( ContainerInterface $c ): void { + public function handle_validation_file( ContainerInterface $c, ApplePayButton $button ): void { + if ( ! $button->is_enabled() ) { + return; + } $env = $c->get( 'onboarding.environment' ); assert( $env instanceof Environment ); $is_sandobx = $env->current_environment_is( Environment::SANDBOX ); diff --git a/modules/ppcp-applepay/src/Assets/DataToAppleButtonScripts.php b/modules/ppcp-applepay/src/Assets/DataToAppleButtonScripts.php index a530ba95f..afe0126ef 100644 --- a/modules/ppcp-applepay/src/Assets/DataToAppleButtonScripts.php +++ b/modules/ppcp-applepay/src/Assets/DataToAppleButtonScripts.php @@ -168,6 +168,7 @@ class DataToAppleButtonScripts { 'totalLabel' => $total_label, ), 'ajax_url' => admin_url( 'admin-ajax.php' ), + 'nonce' => wp_create_nonce( 'woocommerce-process_checkout' ), ); } @@ -215,6 +216,7 @@ class DataToAppleButtonScripts { 'totalLabel' => $total_label, ), 'ajax_url' => admin_url( 'admin-ajax.php' ), + 'nonce' => wp_create_nonce( 'woocommerce-process_checkout' ), ); } diff --git a/modules/ppcp-button/resources/js/modules/ActionHandler/SingleProductActionHandler.js b/modules/ppcp-button/resources/js/modules/ActionHandler/SingleProductActionHandler.js index 42f746b5f..9590e9a57 100644 --- a/modules/ppcp-button/resources/js/modules/ActionHandler/SingleProductActionHandler.js +++ b/modules/ppcp-button/resources/js/modules/ActionHandler/SingleProductActionHandler.js @@ -143,7 +143,7 @@ class SingleProductActionHandler { { this.cartHelper = null; - return (data, actions) => { + return (data, actions, options = {}) => { this.errorHandler.clear(); const onResolve = (purchase_units) => { @@ -178,7 +178,7 @@ class SingleProductActionHandler { }); }; - return this.updateCart.update(onResolve, this.getProducts()); + return this.updateCart.update(onResolve, this.getProducts(), options.updateCartOptions || {}); }; } diff --git a/modules/ppcp-button/resources/js/modules/Helper/UpdateCart.js b/modules/ppcp-button/resources/js/modules/Helper/UpdateCart.js index 4c5b08355..f79f8613b 100644 --- a/modules/ppcp-button/resources/js/modules/Helper/UpdateCart.js +++ b/modules/ppcp-button/resources/js/modules/Helper/UpdateCart.js @@ -11,9 +11,10 @@ class UpdateCart { * * @param onResolve * @param {Product[]} products + * @param {Object} options * @returns {Promise} */ - update(onResolve, products) + update(onResolve, products, options = {}) { return new Promise((resolve, reject) => { fetch( @@ -27,6 +28,7 @@ class UpdateCart { body: JSON.stringify({ nonce: this.nonce, products, + ...options }) } ).then( diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index 2c2762970..049b1ab00 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -395,14 +395,16 @@ class SmartButton implements SmartButtonInterface { return false; } - $get_hook = function ( string $location ): ?array { + $default_pay_order_hook = 'woocommerce_pay_order_before_submit'; + + $get_hook = function ( string $location ) use ( $default_pay_order_hook ): ?array { switch ( $location ) { case 'checkout': return $this->messages_renderer_hook( $location, 'woocommerce_review_order_before_payment', 10 ); case 'cart': return $this->messages_renderer_hook( $location, $this->proceed_to_checkout_button_renderer_hook(), 19 ); case 'pay-now': - return $this->messages_renderer_hook( 'pay_order', 'woocommerce_pay_order_before_submit', 10 ); + return $this->messages_renderer_hook( $location, $default_pay_order_hook, 10 ); case 'product': return $this->messages_renderer_hook( $location, $this->single_product_renderer_hook(), 30 ); case 'shop': @@ -425,6 +427,28 @@ class SmartButton implements SmartButtonInterface { $hook['priority'] ); + // Looks like there are no hooks like woocommerce_review_order_before_payment on the pay for order page, so have to move using JS. + if ( $location === 'pay-now' && $hook['name'] === $default_pay_order_hook && + /** + * The filter returning true if Pay Later messages should be displayed before payment methods + * on the pay for order page, like in checkout. + */ + apply_filters( + 'woocommerce_paypal_payments_put_pay_order_messages_before_payment_methods', + true + ) + ) { + add_action( + 'ppcp_after_pay_order_message_wrapper', + function () { + echo ' +'; + } + ); + } + return true; } @@ -681,11 +705,12 @@ class SmartButton implements SmartButtonInterface { /** * Renders the HTML for the credit messaging. */ - public function message_renderer() { + public function message_renderer(): void { $product = wc_get_product(); - $location = $this->location(); + $location = $this->location(); + $location_hook = $this->location_to_hook( $location ); if ( $location === 'product' && is_a( $product, WC_Product::class ) @@ -697,7 +722,17 @@ class SmartButton implements SmartButtonInterface { return; } + /** + * A hook executed before rendering of the PCP Pay Later messages wrapper. + */ + do_action( "ppcp_before_{$location_hook}_message_wrapper" ); + echo '
'; + + /** + * A hook executed after rendering of the PCP Pay Later messages wrapper. + */ + do_action( "ppcp_after_{$location_hook}_message_wrapper" ); } /** @@ -1203,6 +1238,13 @@ class SmartButton implements SmartButtonInterface { $params['enable-funding'] = implode( ',', array_unique( $enable_funding ) ); } + $locale = $this->settings->has( 'smart_button_language' ) ? $this->settings->get( 'smart_button_language' ) : ''; + $locale = (string) apply_filters( 'woocommerce_paypal_payments_smart_buttons_locale', $locale ); + + if ( $locale ) { + $params['locale'] = $locale; + } + return $params; } @@ -1399,18 +1441,20 @@ class SmartButton implements SmartButtonInterface { * @return array An array with 'name' and 'priority' keys. */ private function messages_renderer_hook( string $location, string $default_hook, int $default_priority ): array { + $location_hook = $this->location_to_hook( $location ); + /** * The filter returning the action name that will be used for rendering Pay Later messages. */ $hook = (string) apply_filters( - "woocommerce_paypal_payments_${location}_messages_renderer_hook", + "woocommerce_paypal_payments_${location_hook}_messages_renderer_hook", $default_hook ); /** * The filter returning the action priority that will be used for rendering Pay Later messages. */ $priority = (int) apply_filters( - "woocommerce_paypal_payments_${location}_messages_renderer_priority", + "woocommerce_paypal_payments_${location_hook}_messages_renderer_priority", $default_priority ); return array( @@ -1724,4 +1768,18 @@ class SmartButton implements SmartButtonInterface { } + /** + * Converts the location name into the name used in hooks. + * + * @param string $location The location. + * @return string + */ + private function location_to_hook( string $location ): string { + switch ( $location ) { + case 'pay-now': + return 'pay_order'; + default: + return $location; + } + } } diff --git a/modules/ppcp-button/src/Endpoint/ChangeCartEndpoint.php b/modules/ppcp-button/src/Endpoint/ChangeCartEndpoint.php index 701076bed..62ad02568 100644 --- a/modules/ppcp-button/src/Endpoint/ChangeCartEndpoint.php +++ b/modules/ppcp-button/src/Endpoint/ChangeCartEndpoint.php @@ -71,6 +71,8 @@ class ChangeCartEndpoint extends AbstractCartEndpoint { * @throws Exception On error. */ protected function handle_data(): bool { + $data = $this->request_data->read_request( $this->nonce() ); + $this->cart_products->set_cart( $this->cart ); $products = $this->products_from_request(); @@ -79,7 +81,9 @@ class ChangeCartEndpoint extends AbstractCartEndpoint { return false; } - $this->shipping->reset_shipping(); + if ( ! ( $data['keepShipping'] ?? false ) ) { + $this->shipping->reset_shipping(); + } if ( ! $this->add_products( $products ) ) { return false; diff --git a/modules/ppcp-button/src/Helper/ContextTrait.php b/modules/ppcp-button/src/Helper/ContextTrait.php index a7381b1b1..db6735424 100644 --- a/modules/ppcp-button/src/Helper/ContextTrait.php +++ b/modules/ppcp-button/src/Helper/ContextTrait.php @@ -34,6 +34,28 @@ trait ContextTrait { return false; } + /** + * Checks WC is_cart() + WC cart ajax requests. + */ + private function is_cart(): bool { + if ( is_cart() ) { + return true; + } + + /** + * The filter returning whether to detect WC cart ajax requests. + */ + if ( apply_filters( 'ppcp_check_ajax_cart', true ) ) { + // phpcs:ignore WordPress.Security + $wc_ajax = $_GET['wc-ajax'] ?? ''; + if ( in_array( $wc_ajax, array( 'update_shipping_method' ), true ) ) { + return true; + } + } + + return false; + } + /** * The current context. * @@ -56,7 +78,7 @@ trait ContextTrait { return 'cart-block'; } - if ( is_cart() ) { + if ( $this->is_cart() ) { return 'cart'; } diff --git a/modules/ppcp-googlepay/resources/js/Context/SingleProductHandler.js b/modules/ppcp-googlepay/resources/js/Context/SingleProductHandler.js index a014f2ae6..6760cfb80 100644 --- a/modules/ppcp-googlepay/resources/js/Context/SingleProductHandler.js +++ b/modules/ppcp-googlepay/resources/js/Context/SingleProductHandler.js @@ -48,6 +48,14 @@ class SingleProductHandler extends BaseHandler { }); } + createOrder() { + return this.actionHandler().configuration().createOrder(null, null, { + 'updateCartOptions': { + 'keepShipping': true + } + }); + } + actionHandler() { return new SingleProductActionHandler( this.ppcpConfig, diff --git a/modules/ppcp-googlepay/services.php b/modules/ppcp-googlepay/services.php index 02a9ccc9d..4d1ef8986 100644 --- a/modules/ppcp-googlepay/services.php +++ b/modules/ppcp-googlepay/services.php @@ -90,6 +90,24 @@ return array( return apply_filters( 'woocommerce_paypal_payments_googlepay_supported_country_currency_matrix', array( + 'CA' => array( + 'AUD', + 'CAD', + 'CHF', + 'CZK', + 'DKK', + 'EUR', + 'GBP', + 'HKD', + 'HUF', + 'JPY', + 'NOK', + 'NZD', + 'PLN', + 'SEK', + 'SGD', + 'USD', + ), 'GB' => array( 'AUD', 'CAD', @@ -108,6 +126,24 @@ return array( 'SGD', 'USD', ), + 'IT' => array( + 'AUD', + 'CAD', + 'CHF', + 'CZK', + 'DKK', + 'EUR', + 'GBP', + 'HKD', + 'HUF', + 'JPY', + 'NOK', + 'NZD', + 'PLN', + 'SEK', + 'SGD', + 'USD', + ), 'US' => array( 'AUD', 'CAD', @@ -116,24 +152,6 @@ return array( 'JPY', 'USD', ), - 'CA' => array( - 'AUD', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'JPY', - 'NOK', - 'NZD', - 'PLN', - 'SEK', - 'SGD', - 'USD', - ), ) ); }, diff --git a/modules/ppcp-googlepay/src/GooglepayModule.php b/modules/ppcp-googlepay/src/GooglepayModule.php index 94f70ea09..083f8eba7 100644 --- a/modules/ppcp-googlepay/src/GooglepayModule.php +++ b/modules/ppcp-googlepay/src/GooglepayModule.php @@ -155,7 +155,8 @@ class GooglepayModule implements ModuleInterface { } ); - } + }, + 1 ); } diff --git a/modules/ppcp-wc-gateway/resources/js/gateway-settings.js b/modules/ppcp-wc-gateway/resources/js/gateway-settings.js index 7886131d9..afec3a426 100644 --- a/modules/ppcp-wc-gateway/resources/js/gateway-settings.js +++ b/modules/ppcp-wc-gateway/resources/js/gateway-settings.js @@ -115,7 +115,7 @@ document.addEventListener( 'currency': PayPalCommerceGatewaySettings.currency, 'integration-date': PayPalCommerceGatewaySettings.integration_date, 'components': PayPalCommerceGatewaySettings.components, - 'enable-funding': ['venmo', 'paylater'], + 'enable-funding': ['venmo', 'paylater'] }; if (PayPalCommerceGatewaySettings.environment === 'sandbox') { @@ -134,6 +134,11 @@ document.addEventListener( settings['disable-funding'] = disabledSources; } + const smartButtonLocale = document.getElementById('ppcp-smart_button_language'); + if (smartButtonLocale?.length > 0 && smartButtonLocale?.value !== '') { + settings['locale'] = smartButtonLocale.value; + } + return settings; } diff --git a/modules/ppcp-wc-gateway/services.php b/modules/ppcp-wc-gateway/services.php index 6c9eef157..063afa9a3 100644 --- a/modules/ppcp-wc-gateway/services.php +++ b/modules/ppcp-wc-gateway/services.php @@ -1422,4 +1422,52 @@ return array( return new DisplayManager( $settings ); } ), + 'wcgateway.wp-paypal-locales-map' => static function( ContainerInterface $container ): array { + return apply_filters( + 'woocommerce_paypal_payments_button_locales', + array( + '' => __( 'Browser language', 'woocommerce-paypal-payments' ), + 'ar_DZ' => __( 'Arabic (Algeria)', 'woocommerce-paypal-payments' ), + 'ar_BH' => __( 'Arabic (Bahrain)', 'woocommerce-paypal-payments' ), + 'ar_EG' => __( 'Arabic (Egypt)', 'woocommerce-paypal-payments' ), + 'ar_JO' => __( 'Arabic (Jordan)', 'woocommerce-paypal-payments' ), + 'ar_KW' => __( 'Arabic (Kuwait)', 'woocommerce-paypal-payments' ), + 'ar_MA' => __( 'Arabic (Morocco)', 'woocommerce-paypal-payments' ), + 'ar_SA' => __( 'Arabic (Saudi Arabia)', 'woocommerce-paypal-payments' ), + 'cs_CZ' => __( 'Czech', 'woocommerce-paypal-payments' ), + 'zh_CN' => __( 'Chinese (Simplified)', 'woocommerce-paypal-payments' ), + 'zh_HK' => __( 'Chinese (Hong Kong)', 'woocommerce-paypal-payments' ), + 'zh_TW' => __( 'Chinese (Traditional)', 'woocommerce-paypal-payments' ), + 'da_DK' => __( 'Danish', 'woocommerce-paypal-payments' ), + 'nl_NL' => __( 'Dutch', 'woocommerce-paypal-payments' ), + 'en_AU' => __( 'English (Australia)', 'woocommerce-paypal-payments' ), + 'en_GB' => __( 'English (United Kingdom)', 'woocommerce-paypal-payments' ), + 'en_US' => __( 'English (United States)', 'woocommerce-paypal-payments' ), + 'fi_FI' => __( 'Finnish', 'woocommerce-paypal-payments' ), + 'fr_CA' => __( 'French (Canada)', 'woocommerce-paypal-payments' ), + 'fr_FR' => __( 'French (France)', 'woocommerce-paypal-payments' ), + 'de_DE' => __( 'German (Germany)', 'woocommerce-paypal-payments' ), + 'de_CH' => __( 'German (Switzerland)', 'woocommerce-paypal-payments' ), + 'de_AT' => __( 'German (Austria)', 'woocommerce-paypal-payments' ), + 'de_LU' => __( 'German (Luxembourg)', 'woocommerce-paypal-payments' ), + 'el_GR' => __( 'Greek', 'woocommerce-paypal-payments' ), + 'he_IL' => __( 'Hebrew', 'woocommerce-paypal-payments' ), + 'hu_HU' => __( 'Hungarian', 'woocommerce-paypal-payments' ), + 'id_ID' => __( 'Indonesian', 'woocommerce-paypal-payments' ), + 'it_IT' => __( 'Italian', 'woocommerce-paypal-payments' ), + 'ja_JP' => __( 'Japanese', 'woocommerce-paypal-payments' ), + 'ko_KR' => __( 'Korean', 'woocommerce-paypal-payments' ), + 'no_NO' => __( 'Norwegian', 'woocommerce-paypal-payments' ), + 'es_ES' => __( 'Spanish (Spain)', 'woocommerce-paypal-payments' ), + 'es_MX' => __( 'Spanish (Mexico)', 'woocommerce-paypal-payments' ), + 'pl_PL' => __( 'Polish', 'woocommerce-paypal-payments' ), + 'pt_BR' => __( 'Portuguese (Brazil)', 'woocommerce-paypal-payments' ), + 'pt_PT' => __( 'Portuguese (Portugal)', 'woocommerce-paypal-payments' ), + 'ru_RU' => __( 'Russian', 'woocommerce-paypal-payments' ), + 'sk_SK' => __( 'Slovak', 'woocommerce-paypal-payments' ), + 'sv_SE' => __( 'Swedish', 'woocommerce-paypal-payments' ), + 'th_TH' => __( 'Thai', 'woocommerce-paypal-payments' ), + ) + ); + }, ); diff --git a/modules/ppcp-wc-gateway/src/Settings/Fields/paypal-smart-button-fields.php b/modules/ppcp-wc-gateway/src/Settings/Fields/paypal-smart-button-fields.php index 82fa1d5f3..b5a7e527c 100644 --- a/modules/ppcp-wc-gateway/src/Settings/Fields/paypal-smart-button-fields.php +++ b/modules/ppcp-wc-gateway/src/Settings/Fields/paypal-smart-button-fields.php @@ -71,6 +71,22 @@ return function ( ContainerInterface $container, array $fields ): array { 'requirements' => array(), 'gateway' => 'paypal', ), + 'smart_button_language' => array( + 'title' => __( 'Smart Button Language', 'woocommerce-paypal-payments' ), + 'type' => 'select', + 'desc_tip' => true, + 'description' => __( + 'The language and region used for the displayed PayPal Smart Buttons. The default value is the current language and region setting in a browser.', + 'woocommerce-paypal-payments' + ), + 'class' => array(), + 'input_class' => array( 'wc-enhanced-select' ), + 'default' => 'en', + 'options' => $container->get( 'wcgateway.wp-paypal-locales-map' ), + 'screens' => array( State::STATE_ONBOARDED ), + 'gateway' => 'paypal', + 'requirements' => array(), + ), 'smart_button_enable_styling_per_location' => array( 'title' => __( 'Customize Smart Buttons Per Location', 'woocommerce-paypal-payments' ), 'type' => 'checkbox', diff --git a/tests/PHPUnit/Button/Endpoint/ChangeCartEndpointTest.php b/tests/PHPUnit/Button/Endpoint/ChangeCartEndpointTest.php index 427e9ce79..dbff4d5c3 100644 --- a/tests/PHPUnit/Button/Endpoint/ChangeCartEndpointTest.php +++ b/tests/PHPUnit/Button/Endpoint/ChangeCartEndpointTest.php @@ -80,6 +80,7 @@ class ChangeCartEndpointTest extends TestCase $requestData = Mockery::mock(RequestData::class); $requestData ->expects('read_request') + ->times(2) ->with(ChangeCartEndpoint::nonce()) ->andReturn($data);