From 7627f38b583096dd7853973cc88c5c161994503e Mon Sep 17 00:00:00 2001 From: Peter Wilson <519727+peterwilsoncc@users.noreply.github.com> Date: Thu, 14 Dec 2023 14:36:25 +1100 Subject: [PATCH 01/20] Send wc_ prefixed fields for express payments. --- .../use-express-checkout-product-handler.js | 12 +++++++++++- client/payment-request/index.js | 15 +++++++++++++-- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/client/checkout/woopay/express-button/use-express-checkout-product-handler.js b/client/checkout/woopay/express-button/use-express-checkout-product-handler.js index 5567cb4a1ae..3df0c452888 100644 --- a/client/checkout/woopay/express-button/use-express-checkout-product-handler.js +++ b/client/checkout/woopay/express-button/use-express-checkout-product-handler.js @@ -2,6 +2,7 @@ * External dependencies */ import validator from 'validator'; +import { applyFilters } from '@wordpress/hooks'; const useExpressCheckoutProductHandler = ( api ) => { const getAttributes = () => { @@ -104,6 +105,14 @@ const useExpressCheckoutProductHandler = ( api ) => { } const addOnForm = document.querySelector( 'form.cart' ); + let allowedFieldNames = applyFilters( + 'wcpayPaymentRequestAllowedFieldNames', + [] + ); + // Ensure allowedFieldNames is an array. + if ( ! Array.isArray( allowedFieldNames ) ) { + allowedFieldNames = [ allowedFieldNames ]; + } if ( addOnForm ) { const formData = new FormData( addOnForm ); @@ -111,7 +120,8 @@ const useExpressCheckoutProductHandler = ( api ) => { formData.forEach( ( value, name ) => { if ( /^addon-/.test( name ) || - /^wc_gc_giftcard_/.test( name ) + /^wc_/.test( name ) || + allowedFieldNames.includes( name ) ) { if ( /\[\]$/.test( name ) ) { const fieldName = name.substring( 0, name.length - 2 ); diff --git a/client/payment-request/index.js b/client/payment-request/index.js index 9578b42517c..2933cac2088 100644 --- a/client/payment-request/index.js +++ b/client/payment-request/index.js @@ -3,7 +3,7 @@ * External dependencies */ import { __ } from '@wordpress/i18n'; -import { doAction } from '@wordpress/hooks'; +import { applyFilters, doAction } from '@wordpress/hooks'; /** * Internal dependencies */ @@ -175,8 +175,19 @@ jQuery( ( $ ) => { // Add addons data to the POST body const formData = $( 'form.cart' ).serializeArray(); + let allowedFieldNames = applyFilters( + 'wcpayPaymentRequestAllowedFieldNames', + [] + ); + // Ensure allowedFieldNames is an array. + if ( ! Array.isArray( allowedFieldNames ) ) { + allowedFieldNames = [ allowedFieldNames ]; + } $.each( formData, ( i, field ) => { - if ( /^addon-/.test( field.name ) ) { + if ( + allowedFieldNames.includes( field.name ) || + /^(addon-|wc_)/.test( field.name ) + ) { if ( /\[\]$/.test( field.name ) ) { const fieldName = field.name.substring( 0, From 21cdddffdc46bc532e8d615b13b5f64ef3a50f51 Mon Sep 17 00:00:00 2001 From: Guilherme Pressutto Date: Wed, 3 Jan 2024 17:47:35 -0300 Subject: [PATCH 02/20] Reload payment request when the deposit option is changed --- client/payment-request/index.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/client/payment-request/index.js b/client/payment-request/index.js index ddd76ff2db2..950d486ba03 100644 --- a/client/payment-request/index.js +++ b/client/payment-request/index.js @@ -440,6 +440,14 @@ jQuery( ( $ ) => { wcpayPaymentRequest.addToCart(); } ); + // WooCommerce Deposits support. + // Trigger the "woocommerce_variation_has_changed" event when the deposit option is changed. + $( 'input[name=wc_deposit_option]' ).on( 'change', () => { + $( 'form:has(input[name=wc_deposit_option])' ).trigger( + 'woocommerce_variation_has_changed' + ); + } ); + $( document.body ).on( 'woocommerce_variation_has_changed', () => { wcpayPaymentRequest.blockPaymentRequestButton(); From f55654e4f5cbf7e58c0aff6aad4333abf772f8e5 Mon Sep 17 00:00:00 2001 From: Guilherme Pressutto Date: Wed, 3 Jan 2024 18:55:22 -0300 Subject: [PATCH 03/20] Handling selected deposit option --- client/payment-request/index.js | 9 +++++++++ ...-payments-payment-request-button-handler.php | 17 +++++++++++++++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/client/payment-request/index.js b/client/payment-request/index.js index 950d486ba03..aabc47545de 100644 --- a/client/payment-request/index.js +++ b/client/payment-request/index.js @@ -313,6 +313,14 @@ jQuery( ( $ ) => { 0 ); + // WC Deposits Support. + const depositObject = {}; + if ( $( 'input[name=wc_deposit_option]' ).length ) { + depositObject.wc_deposit_option = $( + 'input[name=wc_deposit_option]:checked' + ).val(); + } + const data = { product_id: productId, qty: $( '.quantity .qty' ).val(), @@ -320,6 +328,7 @@ jQuery( ( $ ) => { ? wcpayPaymentRequest.getAttributes().data : [], addon_value: addonValue, + ...depositObject, }; return api.paymentRequestGetSelectedProductData( data ); diff --git a/includes/class-wc-payments-payment-request-button-handler.php b/includes/class-wc-payments-payment-request-button-handler.php index 7a30adad550..e1d957cfd0a 100644 --- a/includes/class-wc-payments-payment-request-button-handler.php +++ b/includes/class-wc-payments-payment-request-button-handler.php @@ -202,11 +202,12 @@ public function get_button_height() { * Gets the product total price. * * @param object $product WC_Product_* object. + * @param bool $is_deposit Whether customer is paying a deposit. * @return mixed Total price. * * @throws Invalid_Price_Exception Whenever a product has no price. */ - public function get_product_price( $product ) { + public function get_product_price( $product, ?bool $is_deposit = null ) { // If prices should include tax, using tax inclusive price. if ( $this->express_checkout_helper->cart_prices_include_tax() ) { $base_price = wc_get_price_including_tax( $product ); @@ -214,6 +215,17 @@ public function get_product_price( $product ) { $base_price = wc_get_price_excluding_tax( $product ); } + // If WooCommerce Deposits is active, we need to get the correct price for the product. + if ( class_exists( 'WC_Deposits_Product_Manager' ) && WC_Deposits_Product_Manager::deposits_enabled( $product->get_id() ) ) { + // If is_deposit is null, we use the default deposit type for the product. + if ( is_null( $is_deposit ) ) { + $is_deposit = 'deposit' === WC_Deposits_Product_Manager::get_deposit_type( $product->get_id() ); + } + if ( $is_deposit ) { + $base_price = WC_Deposits_Product_Manager::get_deposit_amount( $product, 0, 'display', $base_price ); + } + } + // Add subscription sign-up fees to product price. $sign_up_fee = 0; $subscription_types = [ @@ -1050,6 +1062,7 @@ public function ajax_get_selected_product_data() { $product = wc_get_product( $product_id ); $variation_id = null; $currency = get_woocommerce_currency(); + $is_deposit = isset( $_POST['wc_deposit_option'] ) ? 'yes' === $_POST['wc_deposit_option'] : null; if ( ! is_a( $product, 'WC_Product' ) ) { /* translators: product ID */ @@ -1077,7 +1090,7 @@ public function ajax_get_selected_product_data() { throw new Exception( sprintf( __( 'You cannot add that amount of "%1$s"; to the cart because there is not enough stock (%2$s remaining).', 'woocommerce-payments' ), $product->get_name(), wc_format_stock_quantity_for_display( $product->get_stock_quantity(), $product ) ) ); } - $price = $this->get_product_price( $product ); + $price = $this->get_product_price( $product, $is_deposit ); $total = $qty * $price + $addon_value; $quantity_label = 1 < $qty ? ' (x' . $qty . ')' : ''; From fedb13b2cb2ba0c7cda5e48f0e2c7785f50fdd19 Mon Sep 17 00:00:00 2001 From: Guilherme Pressutto Date: Wed, 3 Jan 2024 19:05:04 -0300 Subject: [PATCH 04/20] Added changelog entry --- changelog/7907-support-deposit-express-pay | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 changelog/7907-support-deposit-express-pay diff --git a/changelog/7907-support-deposit-express-pay b/changelog/7907-support-deposit-express-pay new file mode 100644 index 00000000000..6851d72dd79 --- /dev/null +++ b/changelog/7907-support-deposit-express-pay @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Added support for WooCommerce Deposits when using Apple Pay and Google Pay From a9b2903abb4bee8d0be356c95e57eb078759b136 Mon Sep 17 00:00:00 2001 From: Guilherme Pressutto Date: Thu, 4 Jan 2024 16:56:43 -0300 Subject: [PATCH 05/20] Fixed deposit tests class --- ...lass-wc-helper-deposit-product-manager.php | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/tests/unit/helpers/class-wc-helper-deposit-product-manager.php b/tests/unit/helpers/class-wc-helper-deposit-product-manager.php index e542442cfe3..9834f10ff87 100644 --- a/tests/unit/helpers/class-wc-helper-deposit-product-manager.php +++ b/tests/unit/helpers/class-wc-helper-deposit-product-manager.php @@ -11,10 +11,25 @@ * This helper class should ONLY be used for unit tests!. */ class WC_Deposits_Product_Manager { - public static function get_deposit_type( WC_Product_Simple $product ) { + /** + * @param WC_Product_Simple|int $product + * @return mixed + */ + public static function get_deposit_type( $product ) { + if ( is_int( $product ) ) { + $product = wc_get_product( $product ); + } return $product->get_meta( '_wc_deposit_type' ); } - public static function deposits_enabled( WC_Product_Simple $product ) { + + /** + * @param WC_Product_Simple|int $product + * @return bool + */ + public static function deposits_enabled( $product ) { + if ( is_int( $product ) ) { + $product = wc_get_product( $product ); + } return true === $product->get_meta( '_wc_deposits_enabled' ); } } From 1f13e7e81ed313c3457c376cab4c203ca7fac168 Mon Sep 17 00:00:00 2001 From: Guilherme Pressutto Date: Thu, 4 Jan 2024 17:17:29 -0300 Subject: [PATCH 06/20] Fixed deposit tests class --- ...class-wc-helper-deposit-product-manager.php | 8 ++++---- ...payments-payment-request-button-handler.php | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/tests/unit/helpers/class-wc-helper-deposit-product-manager.php b/tests/unit/helpers/class-wc-helper-deposit-product-manager.php index 9834f10ff87..d0d8fb1398c 100644 --- a/tests/unit/helpers/class-wc-helper-deposit-product-manager.php +++ b/tests/unit/helpers/class-wc-helper-deposit-product-manager.php @@ -16,8 +16,8 @@ class WC_Deposits_Product_Manager { * @return mixed */ public static function get_deposit_type( $product ) { - if ( is_int( $product ) ) { - $product = wc_get_product( $product ); + if ( ! is_object( $product ) ) { + $product = apply_filters( 'test_deposit_get_product', wc_get_product( $product ) ); } return $product->get_meta( '_wc_deposit_type' ); } @@ -27,8 +27,8 @@ public static function get_deposit_type( $product ) { * @return bool */ public static function deposits_enabled( $product ) { - if ( is_int( $product ) ) { - $product = wc_get_product( $product ); + if ( ! is_object( $product ) ) { + $product = apply_filters( 'test_deposit_get_product', wc_get_product( $product ) ); } return true === $product->get_meta( '_wc_deposits_enabled' ); } diff --git a/tests/unit/test-class-wc-payments-payment-request-button-handler.php b/tests/unit/test-class-wc-payments-payment-request-button-handler.php index e4d7412bf15..a520ab2e10b 100644 --- a/tests/unit/test-class-wc-payments-payment-request-button-handler.php +++ b/tests/unit/test-class-wc-payments-payment-request-button-handler.php @@ -446,6 +446,12 @@ public function provide_get_product_tax_tests() { public function test_get_product_price_includes_subscription_sign_up_fee() { $mock_product = $this->create_mock_subscription( 'subscription' ); + add_filter( + 'test_deposit_get_product', + function() use ( $mock_product ) { + return $mock_product; + } + ); // We have a helper because we are not loading subscriptions. WC_Subscriptions_Product::set_sign_up_fee( 10 ); @@ -458,6 +464,12 @@ public function test_get_product_price_includes_subscription_sign_up_fee() { public function test_get_product_price_includes_variable_subscription_sign_up_fee() { $mock_product = $this->create_mock_subscription( 'subscription_variation' ); + add_filter( + 'test_deposit_get_product', + function() use ( $mock_product ) { + return $mock_product; + } + ); // We have a helper because we are not loading subscriptions. WC_Subscriptions_Product::set_sign_up_fee( 10 ); @@ -483,6 +495,12 @@ public function test_get_product_price_throws_exception_for_products_without_pri public function test_get_product_price_throws_exception_for_a_non_numeric_signup_fee() { $mock_product = $this->create_mock_subscription( 'subscription' ); + add_filter( + 'test_deposit_get_product', + function() use ( $mock_product ) { + return $mock_product; + } + ); WC_Subscriptions_Product::set_sign_up_fee( 'a' ); $this->expectException( WCPay\Exceptions\Invalid_Price_Exception::class ); From e37202f27452616347251bdd0d6effc2ad1158e2 Mon Sep 17 00:00:00 2001 From: Guilherme Pressutto Date: Mon, 8 Jan 2024 11:34:36 -0300 Subject: [PATCH 07/20] Removed filter + sanitizing POST string --- .../use-express-checkout-product-handler.js | 15 +-------------- client/payment-request/index.js | 15 ++------------- ...wc-payments-payment-request-button-handler.php | 2 +- 3 files changed, 4 insertions(+), 28 deletions(-) diff --git a/client/checkout/woopay/express-button/use-express-checkout-product-handler.js b/client/checkout/woopay/express-button/use-express-checkout-product-handler.js index 3df0c452888..0cc5271de7b 100644 --- a/client/checkout/woopay/express-button/use-express-checkout-product-handler.js +++ b/client/checkout/woopay/express-button/use-express-checkout-product-handler.js @@ -2,7 +2,6 @@ * External dependencies */ import validator from 'validator'; -import { applyFilters } from '@wordpress/hooks'; const useExpressCheckoutProductHandler = ( api ) => { const getAttributes = () => { @@ -105,24 +104,12 @@ const useExpressCheckoutProductHandler = ( api ) => { } const addOnForm = document.querySelector( 'form.cart' ); - let allowedFieldNames = applyFilters( - 'wcpayPaymentRequestAllowedFieldNames', - [] - ); - // Ensure allowedFieldNames is an array. - if ( ! Array.isArray( allowedFieldNames ) ) { - allowedFieldNames = [ allowedFieldNames ]; - } if ( addOnForm ) { const formData = new FormData( addOnForm ); formData.forEach( ( value, name ) => { - if ( - /^addon-/.test( name ) || - /^wc_/.test( name ) || - allowedFieldNames.includes( name ) - ) { + if ( /^(addon-|wc_)/.test( name ) ) { if ( /\[\]$/.test( name ) ) { const fieldName = name.substring( 0, name.length - 2 ); diff --git a/client/payment-request/index.js b/client/payment-request/index.js index aabc47545de..4c382128fc2 100644 --- a/client/payment-request/index.js +++ b/client/payment-request/index.js @@ -3,7 +3,7 @@ * External dependencies */ import { __ } from '@wordpress/i18n'; -import { applyFilters, doAction } from '@wordpress/hooks'; +import { doAction } from '@wordpress/hooks'; import { debounce } from 'lodash'; /** * Internal dependencies @@ -189,19 +189,8 @@ jQuery( ( $ ) => { // Add addons data to the POST body const formData = $( 'form.cart' ).serializeArray(); - let allowedFieldNames = applyFilters( - 'wcpayPaymentRequestAllowedFieldNames', - [] - ); - // Ensure allowedFieldNames is an array. - if ( ! Array.isArray( allowedFieldNames ) ) { - allowedFieldNames = [ allowedFieldNames ]; - } $.each( formData, ( i, field ) => { - if ( - allowedFieldNames.includes( field.name ) || - /^(addon-|wc_)/.test( field.name ) - ) { + if ( /^(addon-|wc_)/.test( field.name ) ) { if ( /\[\]$/.test( field.name ) ) { const fieldName = field.name.substring( 0, diff --git a/includes/class-wc-payments-payment-request-button-handler.php b/includes/class-wc-payments-payment-request-button-handler.php index e1d957cfd0a..bb4fe7ccc40 100644 --- a/includes/class-wc-payments-payment-request-button-handler.php +++ b/includes/class-wc-payments-payment-request-button-handler.php @@ -1062,7 +1062,7 @@ public function ajax_get_selected_product_data() { $product = wc_get_product( $product_id ); $variation_id = null; $currency = get_woocommerce_currency(); - $is_deposit = isset( $_POST['wc_deposit_option'] ) ? 'yes' === $_POST['wc_deposit_option'] : null; + $is_deposit = isset( $_POST['wc_deposit_option'] ) ? 'yes' === sanitize_text_field( wp_unslash( $_POST['wc_deposit_option'] ) ) : null; if ( ! is_a( $product, 'WC_Product' ) ) { /* translators: product ID */ From 9014f3ca074778b63b94eb0ac103e309152ed6aa Mon Sep 17 00:00:00 2001 From: Guilherme Pressutto Date: Mon, 8 Jan 2024 12:34:14 -0300 Subject: [PATCH 08/20] Added tests for payment request + deposits --- ...ayments-payment-request-button-handler.php | 2 +- ...lass-wc-helper-deposit-product-manager.php | 53 ++++++++++++-- ...ayments-payment-request-button-handler.php | 69 +++++++++++++++++++ 3 files changed, 119 insertions(+), 5 deletions(-) diff --git a/includes/class-wc-payments-payment-request-button-handler.php b/includes/class-wc-payments-payment-request-button-handler.php index bb4fe7ccc40..92a19a0138a 100644 --- a/includes/class-wc-payments-payment-request-button-handler.php +++ b/includes/class-wc-payments-payment-request-button-handler.php @@ -219,7 +219,7 @@ public function get_product_price( $product, ?bool $is_deposit = null ) { if ( class_exists( 'WC_Deposits_Product_Manager' ) && WC_Deposits_Product_Manager::deposits_enabled( $product->get_id() ) ) { // If is_deposit is null, we use the default deposit type for the product. if ( is_null( $is_deposit ) ) { - $is_deposit = 'deposit' === WC_Deposits_Product_Manager::get_deposit_type( $product->get_id() ); + $is_deposit = 'deposit' === WC_Deposits_Product_Manager::get_deposit_selected_type( $product->get_id() ); } if ( $is_deposit ) { $base_price = WC_Deposits_Product_Manager::get_deposit_amount( $product, 0, 'display', $base_price ); diff --git a/tests/unit/helpers/class-wc-helper-deposit-product-manager.php b/tests/unit/helpers/class-wc-helper-deposit-product-manager.php index d0d8fb1398c..f4693cf9402 100644 --- a/tests/unit/helpers/class-wc-helper-deposit-product-manager.php +++ b/tests/unit/helpers/class-wc-helper-deposit-product-manager.php @@ -16,20 +16,65 @@ class WC_Deposits_Product_Manager { * @return mixed */ public static function get_deposit_type( $product ) { - if ( ! is_object( $product ) ) { - $product = apply_filters( 'test_deposit_get_product', wc_get_product( $product ) ); - } + $product = self::get_product( $product ); return $product->get_meta( '_wc_deposit_type' ); } + /** + * @param WC_Product_Simple|int $product + * @return mixed + */ + public static function get_deposit_selected_type( $product ) { + $product = self::get_product( $product ); + return $product->get_meta( '_wc_deposit_selected_type' ); + } + /** * @param WC_Product_Simple|int $product * @return bool */ public static function deposits_enabled( $product ) { + $product = self::get_product( $product ); + $setting = $product->get_meta( '_wc_deposit_enabled' ); + return 'optional' === $setting || 'forced' === $setting; + } + + /** + * @param WC_Product_Simple|int $product + * @return mixed + */ + public static function get_deposit_amount( $product, $plan_id = 0, $context = 'display', $product_price = null ) { + $product = self::get_product( $product ); + $type = self::get_deposit_type( $product ); + $percentage = 'percent' === $type; + $amount = $product->get_meta( '_wc_deposit_amount' ); + + if ( ! $amount ) { + $amount = get_option( 'wc_deposits_default_amount' ); + } + + if ( ! $amount ) { + return false; + } + + if ( $percentage ) { + $product_price = is_null( $product_price ) ? $product->get_price() : $product_price; + $amount = ( $product_price / 100 ) * $amount; + } + + $price = $amount; + return wc_format_decimal( $price ); + } + + /** + * @param $product + * @return mixed|null + */ + public static function get_product( $product ) { if ( ! is_object( $product ) ) { $product = apply_filters( 'test_deposit_get_product', wc_get_product( $product ) ); } - return true === $product->get_meta( '_wc_deposits_enabled' ); + + return $product; } } diff --git a/tests/unit/test-class-wc-payments-payment-request-button-handler.php b/tests/unit/test-class-wc-payments-payment-request-button-handler.php index a520ab2e10b..ed5b840cd64 100644 --- a/tests/unit/test-class-wc-payments-payment-request-button-handler.php +++ b/tests/unit/test-class-wc-payments-payment-request-button-handler.php @@ -345,6 +345,75 @@ public function test_get_product_price_returns_simple_price() { ); } + public function test_get_product_price_returns_deposit_amount() { + $product_price = 10; + $this->simple_product->set_price( $product_price ); + + $this->assertEquals( + $product_price, + $this->pr->get_product_price( $this->simple_product, false ), + 'When deposit is disabled, the regular price should be returned.' + ); + $this->assertEquals( + $product_price, + $this->pr->get_product_price( $this->simple_product, true ), + 'When deposit is enabled, but the product has no setting for deposit, the regular price should be returned.' + ); + + $this->simple_product->update_meta_data( '_wc_deposit_enabled', 'optional' ); + $this->simple_product->update_meta_data( '_wc_deposit_type', 'percent' ); + $this->simple_product->update_meta_data( '_wc_deposit_amount', 50 ); + $this->simple_product->save_meta_data(); + + $this->assertEquals( + $product_price, + $this->pr->get_product_price( $this->simple_product, false ), + 'When deposit is disabled, the regular price should be returned.' + ); + $this->assertEquals( + $product_price * 0.5, + $this->pr->get_product_price( $this->simple_product, true ), + 'When deposit is enabled, the deposit price should be returned.' + ); + + $this->simple_product->delete_meta_data( '_wc_deposit_amount' ); + $this->simple_product->delete_meta_data( '_wc_deposit_type' ); + $this->simple_product->delete_meta_data( '_wc_deposit_enabled' ); + $this->simple_product->save_meta_data(); + } + + public function test_get_product_price_returns_deposit_amount_default_values() { + $product_price = 10; + $this->simple_product->set_price( $product_price ); + + $this->assertEquals( + $product_price, + $this->pr->get_product_price( $this->simple_product ), + 'When deposit is disabled by default, the regular price should be returned.' + ); + + $this->simple_product->update_meta_data( '_wc_deposit_enabled', 'optional' ); + $this->simple_product->update_meta_data( '_wc_deposit_type', 'percent' ); + $this->simple_product->update_meta_data( '_wc_deposit_amount', 50 ); + $this->simple_product->update_meta_data( '_wc_deposit_selected_type', 'full' ); + $this->simple_product->save_meta_data(); + + $this->assertEquals( + $product_price, + $this->pr->get_product_price( $this->simple_product ), + 'When deposit is optional and disabled by default, the regular price should be returned.' + ); + + $this->simple_product->update_meta_data( '_wc_deposit_selected_type', 'deposit' ); + $this->simple_product->save_meta_data(); + + $this->assertEquals( + $product_price * 0.5, + $this->pr->get_product_price( $this->simple_product ), + 'When deposit is optional and selected by default, the deposit price should be returned.' + ); + } + /** * @dataProvider provide_get_product_tax_tests */ From 63afd86f54e4e06f61a7208973bea6bb6e570b15 Mon Sep 17 00:00:00 2001 From: Guilherme Pressutto Date: Tue, 9 Jan 2024 15:45:02 -0300 Subject: [PATCH 09/20] Apply suggestions from code review Co-authored-by: Peter Wilson <519727+peterwilsoncc@users.noreply.github.com> --- client/payment-request/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/payment-request/index.js b/client/payment-request/index.js index 4c382128fc2..00aee1b0e66 100644 --- a/client/payment-request/index.js +++ b/client/payment-request/index.js @@ -440,8 +440,8 @@ jQuery( ( $ ) => { // WooCommerce Deposits support. // Trigger the "woocommerce_variation_has_changed" event when the deposit option is changed. - $( 'input[name=wc_deposit_option]' ).on( 'change', () => { - $( 'form:has(input[name=wc_deposit_option])' ).trigger( + $( 'input[name=wc_deposit_option],input[name=wc_deposit_payment_plan]' ).on( 'change', () => { + $( 'form' ).has('input[name=wc_deposit_option],input[name=wc_deposit_payment_plan]').trigger( 'woocommerce_variation_has_changed' ); } ); From 128de05b7a6fd8105abc67bb803e5a44e6c0731e Mon Sep 17 00:00:00 2001 From: Guilherme Pressutto Date: Tue, 9 Jan 2024 16:20:04 -0300 Subject: [PATCH 10/20] Fixed existing tests --- .../compatibility/test-class-woocommerce-deposits.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/multi-currency/compatibility/test-class-woocommerce-deposits.php b/tests/unit/multi-currency/compatibility/test-class-woocommerce-deposits.php index e9e3c8a082e..136ede0b717 100644 --- a/tests/unit/multi-currency/compatibility/test-class-woocommerce-deposits.php +++ b/tests/unit/multi-currency/compatibility/test-class-woocommerce-deposits.php @@ -93,7 +93,7 @@ function( $input ) { $amount = 10.00; $product = WC_Helper_Product::create_simple_product(); - $product->add_meta_data( '_wc_deposits_enabled', true ); + $product->add_meta_data( '_wc_deposit_enabled', 'optional' ); $product->add_meta_data( '_wc_deposit_type', 'plan' ); $product->save(); @@ -112,7 +112,7 @@ public function test_maybe_convert_product_prices_for_deposits() { ->willReturn( true ); $product = WC_Helper_Product::create_simple_product(); - $product->add_meta_data( '_wc_deposits_enabled', true ); + $product->add_meta_data( '_wc_deposit_enabled', 'optional' ); $product->add_meta_data( '_wc_deposit_type', 'plan' ); $product->save(); From 36f9e95f71a2ff655db372239cb520f59eb5ee25 Mon Sep 17 00:00:00 2001 From: Guilherme Pressutto Date: Tue, 9 Jan 2024 16:20:19 -0300 Subject: [PATCH 11/20] Added deposit plans support --- ...ayments-payment-request-button-handler.php | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/includes/class-wc-payments-payment-request-button-handler.php b/includes/class-wc-payments-payment-request-button-handler.php index 0b8442002e5..f8509228d6f 100644 --- a/includes/class-wc-payments-payment-request-button-handler.php +++ b/includes/class-wc-payments-payment-request-button-handler.php @@ -203,11 +203,12 @@ public function get_button_height() { * * @param object $product WC_Product_* object. * @param bool $is_deposit Whether customer is paying a deposit. + * @param int $deposit_plan_id The ID of the deposit plan. * @return mixed Total price. * * @throws Invalid_Price_Exception Whenever a product has no price. */ - public function get_product_price( $product, ?bool $is_deposit = null ) { + public function get_product_price( $product, ?bool $is_deposit = null, int $deposit_plan_id = 0 ) { // If prices should include tax, using tax inclusive price. if ( $this->express_checkout_helper->cart_prices_include_tax() ) { $base_price = wc_get_price_including_tax( $product ); @@ -222,7 +223,7 @@ public function get_product_price( $product, ?bool $is_deposit = null ) { $is_deposit = 'deposit' === WC_Deposits_Product_Manager::get_deposit_selected_type( $product->get_id() ); } if ( $is_deposit ) { - $base_price = WC_Deposits_Product_Manager::get_deposit_amount( $product, 0, 'display', $base_price ); + $base_price = WC_Deposits_Product_Manager::get_deposit_amount( $product, $deposit_plan_id, 'display', $base_price ); } } @@ -1057,13 +1058,14 @@ public function ajax_get_selected_product_data() { check_ajax_referer( 'wcpay-get-selected-product-data', 'security' ); try { - $product_id = isset( $_POST['product_id'] ) ? absint( $_POST['product_id'] ) : false; - $qty = ! isset( $_POST['qty'] ) ? 1 : apply_filters( 'woocommerce_add_to_cart_quantity', absint( $_POST['qty'] ), $product_id ); - $addon_value = isset( $_POST['addon_value'] ) ? max( (float) $_POST['addon_value'], 0 ) : 0; - $product = wc_get_product( $product_id ); - $variation_id = null; - $currency = get_woocommerce_currency(); - $is_deposit = isset( $_POST['wc_deposit_option'] ) ? 'yes' === sanitize_text_field( wp_unslash( $_POST['wc_deposit_option'] ) ) : null; + $product_id = isset( $_POST['product_id'] ) ? absint( $_POST['product_id'] ) : false; + $qty = ! isset( $_POST['qty'] ) ? 1 : apply_filters( 'woocommerce_add_to_cart_quantity', absint( $_POST['qty'] ), $product_id ); + $addon_value = isset( $_POST['addon_value'] ) ? max( (float) $_POST['addon_value'], 0 ) : 0; + $product = wc_get_product( $product_id ); + $variation_id = null; + $currency = get_woocommerce_currency(); + $is_deposit = isset( $_POST['wc_deposit_option'] ) ? 'yes' === sanitize_text_field( wp_unslash( $_POST['wc_deposit_option'] ) ) : null; + $deposit_plan_id = isset( $_POST['wc_deposit_payment_plan'] ) ? absint( $_POST['wc_deposit_payment_plan'] ) : 0; if ( ! is_a( $product, 'WC_Product' ) ) { /* translators: product ID */ @@ -1091,7 +1093,7 @@ public function ajax_get_selected_product_data() { throw new Exception( sprintf( __( 'You cannot add that amount of "%1$s"; to the cart because there is not enough stock (%2$s remaining).', 'woocommerce-payments' ), $product->get_name(), wc_format_stock_quantity_for_display( $product->get_stock_quantity(), $product ) ) ); } - $price = $this->get_product_price( $product, $is_deposit ); + $price = $this->get_product_price( $product, $is_deposit, $deposit_plan_id ); $total = $qty * $price + $addon_value; $quantity_label = 1 < $qty ? ' (x' . $qty . ')' : ''; From 8ba894bfd2c7faed0326f374337d8f8aa15d2fb5 Mon Sep 17 00:00:00 2001 From: Guilherme Pressutto Date: Tue, 9 Jan 2024 16:31:05 -0300 Subject: [PATCH 12/20] JS style fix --- client/payment-request/index.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/client/payment-request/index.js b/client/payment-request/index.js index 00aee1b0e66..0265ab7b3e3 100644 --- a/client/payment-request/index.js +++ b/client/payment-request/index.js @@ -440,10 +440,14 @@ jQuery( ( $ ) => { // WooCommerce Deposits support. // Trigger the "woocommerce_variation_has_changed" event when the deposit option is changed. - $( 'input[name=wc_deposit_option],input[name=wc_deposit_payment_plan]' ).on( 'change', () => { - $( 'form' ).has('input[name=wc_deposit_option],input[name=wc_deposit_payment_plan]').trigger( - 'woocommerce_variation_has_changed' - ); + $( + 'input[name=wc_deposit_option],input[name=wc_deposit_payment_plan]' + ).on( 'change', () => { + $( 'form' ) + .has( + 'input[name=wc_deposit_option],input[name=wc_deposit_payment_plan]' + ) + .trigger( 'woocommerce_variation_has_changed' ); } ); $( document.body ).on( 'woocommerce_variation_has_changed', () => { From eff996efeafdabb02191ab5a71b96726137174ed Mon Sep 17 00:00:00 2001 From: Peter Wilson <519727+peterwilsoncc@users.noreply.github.com> Date: Thu, 11 Jan 2024 10:44:00 +1100 Subject: [PATCH 13/20] Ensure payment plan is specified. --- .../class-wc-payments-payment-request-button-handler.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/includes/class-wc-payments-payment-request-button-handler.php b/includes/class-wc-payments-payment-request-button-handler.php index f8509228d6f..0fce25f8e1b 100644 --- a/includes/class-wc-payments-payment-request-button-handler.php +++ b/includes/class-wc-payments-payment-request-button-handler.php @@ -223,6 +223,15 @@ public function get_product_price( $product, ?bool $is_deposit = null, int $depo $is_deposit = 'deposit' === WC_Deposits_Product_Manager::get_deposit_selected_type( $product->get_id() ); } if ( $is_deposit ) { + $deposit_type = WC_Deposits_Product_Manager::get_deposit_type( $product->get_id() ); + if ( 'plan' === $deposit_type && 0 === $deposit_plan_id ) { + // Default to first (default) plan if no plan is specified. + $available_plan_ids = WC_Deposits_Plans_Manager::get_plan_ids_for_product( $product->get_id() ); + if ( ! empty( $available_plan_ids ) ) { + $deposit_plan_id = $available_plan_ids[0]; + } + } + $base_price = WC_Deposits_Product_Manager::get_deposit_amount( $product, $deposit_plan_id, 'display', $base_price ); } } From 980c37418b9499812970ab57875523a6e2f335e6 Mon Sep 17 00:00:00 2001 From: Guilherme Pressutto Date: Wed, 10 Jan 2024 10:59:41 -0300 Subject: [PATCH 14/20] Fixed deposit plans frontend --- client/payment-request/index.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/client/payment-request/index.js b/client/payment-request/index.js index 5e2dd2ce4f4..7af7102d620 100644 --- a/client/payment-request/index.js +++ b/client/payment-request/index.js @@ -309,6 +309,11 @@ jQuery( ( $ ) => { 'input[name=wc_deposit_option]:checked' ).val(); } + if ( $( 'input[name=wc_deposit_payment_plan]' ).length ) { + depositObject.wc_deposit_payment_plan = $( + 'input[name=wc_deposit_payment_plan]:checked' + ).val(); + } const data = { product_id: productId, From 9c54d6b3a83866eb5097ba038885852b5ecede2a Mon Sep 17 00:00:00 2001 From: Guilherme Pressutto Date: Wed, 31 Jan 2024 18:33:35 -0300 Subject: [PATCH 15/20] Fixed itemized amount for deposits and apple pay --- ...ss-wc-payments-express-checkout-button-helper.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/includes/express-checkout/class-wc-payments-express-checkout-button-helper.php b/includes/express-checkout/class-wc-payments-express-checkout-button-helper.php index 07ecaf1b3ac..feb0050ff28 100644 --- a/includes/express-checkout/class-wc-payments-express-checkout-button-helper.php +++ b/includes/express-checkout/class-wc-payments-express-checkout-button-helper.php @@ -132,11 +132,19 @@ public function build_display_items( $itemized_display_items = false ) { $product_name = $cart_item['data']->get_name(); - $item_tax = $this->cart_prices_include_tax() ? ( $cart_item['line_subtotal_tax'] ?? 0 ) : 0; + $item_tax = $this->cart_prices_include_tax() ? ( $cart_item['line_subtotal_tax'] ?? 0 ) : 0; + $product_total = $amount + $item_tax; + + // Currently deposits and express checkout are not 100% compatible. + // Taxes are not calculated correctly when a deposit is used. + // Check https://github.com/Automattic/woocommerce-payments/pull/7910 for more details. + if ( isset( $cart_item['is_deposit'] ) && $cart_item['is_deposit'] ) { + $product_total = $cart_item['deposit_amount']; + } $item = [ 'label' => $product_name . $quantity_label, - 'amount' => WC_Payments_Utils::prepare_amount( $amount + $item_tax, $currency ), + 'amount' => WC_Payments_Utils::prepare_amount( $product_total, $currency ), ]; $items[] = $item; From 90a9c251bf7f5fdd71a66dc0f34c972599ea4aa2 Mon Sep 17 00:00:00 2001 From: Guilherme Pressutto Date: Mon, 5 Feb 2024 14:21:59 -0300 Subject: [PATCH 16/20] Checking if plan is available for product --- ...-payments-payment-request-button-handler.php | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/includes/class-wc-payments-payment-request-button-handler.php b/includes/class-wc-payments-payment-request-button-handler.php index df4acf157fe..3cb54571878 100644 --- a/includes/class-wc-payments-payment-request-button-handler.php +++ b/includes/class-wc-payments-payment-request-button-handler.php @@ -225,16 +225,17 @@ public function get_product_price( $product, ?bool $is_deposit = null, int $depo $is_deposit = 'deposit' === WC_Deposits_Product_Manager::get_deposit_selected_type( $product->get_id() ); } if ( $is_deposit ) { - $deposit_type = WC_Deposits_Product_Manager::get_deposit_type( $product->get_id() ); - if ( 'plan' === $deposit_type && 0 === $deposit_plan_id ) { - // Default to first (default) plan if no plan is specified. - $available_plan_ids = WC_Deposits_Plans_Manager::get_plan_ids_for_product( $product->get_id() ); - if ( ! empty( $available_plan_ids ) ) { - $deposit_plan_id = $available_plan_ids[0]; - } + $deposit_type = WC_Deposits_Product_Manager::get_deposit_type( $product->get_id() ); + $available_plan_ids = WC_Deposits_Plans_Manager::get_plan_ids_for_product( $product->get_id() ); + // Default to first (default) plan if no plan is specified. + if ( 'plan' === $deposit_type && 0 === $deposit_plan_id && ! empty( $available_plan_ids ) ) { + $deposit_plan_id = $available_plan_ids[0]; } - $base_price = WC_Deposits_Product_Manager::get_deposit_amount( $product, $deposit_plan_id, 'display', $base_price ); + // Ensure the selected plan is available for the product. + if ( in_array( $deposit_plan_id, $available_plan_ids, true ) ) { + $base_price = WC_Deposits_Product_Manager::get_deposit_amount( $product, $deposit_plan_id, 'display', $base_price ); + } } } From 804ebed2c96906d344e84ff930cb9c91e654e535 Mon Sep 17 00:00:00 2001 From: Guilherme Pressutto Date: Mon, 19 Feb 2024 15:00:00 -0300 Subject: [PATCH 17/20] Revert "Fixed itemized amount for deposits and apple pay" This reverts commit 9c54d6b3a83866eb5097ba038885852b5ecede2a. --- ...ss-wc-payments-express-checkout-button-helper.php | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/includes/express-checkout/class-wc-payments-express-checkout-button-helper.php b/includes/express-checkout/class-wc-payments-express-checkout-button-helper.php index 554ec7028cb..4c42635d102 100644 --- a/includes/express-checkout/class-wc-payments-express-checkout-button-helper.php +++ b/includes/express-checkout/class-wc-payments-express-checkout-button-helper.php @@ -178,19 +178,11 @@ public function build_display_items( $itemized_display_items = false ) { $product_name = $cart_item['data']->get_name(); - $item_tax = $this->cart_prices_include_tax() ? ( $cart_item['line_subtotal_tax'] ?? 0 ) : 0; - $product_total = $amount + $item_tax; - - // Currently deposits and express checkout are not 100% compatible. - // Taxes are not calculated correctly when a deposit is used. - // Check https://github.com/Automattic/woocommerce-payments/pull/7910 for more details. - if ( isset( $cart_item['is_deposit'] ) && $cart_item['is_deposit'] ) { - $product_total = $cart_item['deposit_amount']; - } + $item_tax = $this->cart_prices_include_tax() ? ( $cart_item['line_subtotal_tax'] ?? 0 ) : 0; $item = [ 'label' => $product_name . $quantity_label, - 'amount' => WC_Payments_Utils::prepare_amount( $product_total, $currency ), + 'amount' => WC_Payments_Utils::prepare_amount( $amount + $item_tax, $currency ), ]; $items[] = $item; From 369509e13db8cb22bb1d702087c823079cb5876a Mon Sep 17 00:00:00 2001 From: Guilherme Pressutto Date: Mon, 19 Feb 2024 15:14:59 -0300 Subject: [PATCH 18/20] Fixed wcpay_payment_request_hide_itemization filter --- .../class-wc-payments-express-checkout-button-helper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/express-checkout/class-wc-payments-express-checkout-button-helper.php b/includes/express-checkout/class-wc-payments-express-checkout-button-helper.php index 4c42635d102..617a0fff352 100644 --- a/includes/express-checkout/class-wc-payments-express-checkout-button-helper.php +++ b/includes/express-checkout/class-wc-payments-express-checkout-button-helper.php @@ -170,7 +170,7 @@ public function build_display_items( $itemized_display_items = false ) { $currency = get_woocommerce_currency(); // Default show only subtotal instead of itemization. - if ( ! apply_filters( 'wcpay_payment_request_hide_itemization', true ) || $itemized_display_items ) { + if ( ! apply_filters( 'wcpay_payment_request_hide_itemization', ! $itemized_display_items ) ) { foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) { $amount = $cart_item['line_subtotal']; $subtotal += $cart_item['line_subtotal']; From 1663d7620598435edf80fd718c07e014f2557c20 Mon Sep 17 00:00:00 2001 From: Guilherme Pressutto Date: Mon, 19 Feb 2024 15:49:30 -0300 Subject: [PATCH 19/20] Only checking if the plan is valid when using a plan --- includes/class-wc-payments-payment-request-button-handler.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/class-wc-payments-payment-request-button-handler.php b/includes/class-wc-payments-payment-request-button-handler.php index 3c679d15d2b..43295f01f66 100644 --- a/includes/class-wc-payments-payment-request-button-handler.php +++ b/includes/class-wc-payments-payment-request-button-handler.php @@ -233,7 +233,7 @@ public function get_product_price( $product, ?bool $is_deposit = null, int $depo } // Ensure the selected plan is available for the product. - if ( in_array( $deposit_plan_id, $available_plan_ids, true ) ) { + if ( 0 === $deposit_plan_id || in_array( $deposit_plan_id, $available_plan_ids, true ) ) { $base_price = WC_Deposits_Product_Manager::get_deposit_amount( $product, $deposit_plan_id, 'display', $base_price ); } } From 5d8c4aedf704264a317d3190e3b80ee710d02690 Mon Sep 17 00:00:00 2001 From: Guilherme Pressutto Date: Tue, 20 Feb 2024 15:41:38 -0300 Subject: [PATCH 20/20] Fixed tests --- ...lass-wc-helper-deposit-product-manager.php | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tests/unit/helpers/class-wc-helper-deposit-product-manager.php b/tests/unit/helpers/class-wc-helper-deposit-product-manager.php index f4693cf9402..eefde552fb7 100644 --- a/tests/unit/helpers/class-wc-helper-deposit-product-manager.php +++ b/tests/unit/helpers/class-wc-helper-deposit-product-manager.php @@ -5,6 +5,8 @@ * @package WooCommerce\Payments\Tests */ +// phpcs:disable Generic.Files.OneObjectStructurePerFile.MultipleFound + /** * Class WC_Deposits_Product_Manager. * @@ -78,3 +80,30 @@ public static function get_product( $product ) { return $product; } } + +/** + * Class WC_Deposits_Plans_Manager. + */ +class WC_Deposits_Plans_Manager { + /** + * Get plan ids assigned to a product. + * + * @param int $product_id Product ID. + * @return int[] + */ + public static function get_plan_ids_for_product( $product_id ) { + $product = WC_Deposits_Product_Manager::get_product( $product_id ); + $map = array_map( 'absint', array_filter( (array) $product->get_meta( '_wc_deposit_payment_plans' ) ) ); + if ( count( $map ) <= 0 ) { + $map = self::get_default_plan_ids(); + } + return $map; + } + + /** + * Get the default plan IDs. + */ + public static function get_default_plan_ids() { + return array_map( 'absint', array_filter( (array) get_option( 'wc_deposits_default_plans', [] ) ) ); + } +}