-
Notifications
You must be signed in to change notification settings - Fork 68
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Updated WooPayments MultiCurrency integration with Product Add-Ons #9070
Changes from all commits
8ee3a00
016700b
70d2129
857c53c
bd3eab7
2343d0c
2e183c2
2bb93f4
4a21184
375cba1
1ca24c4
52388fd
e2c12ee
7677ad0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
Significance: minor | ||
Type: update | ||
|
||
Updated the integration between WooPayments Multi-Currency and Product Add-Ons. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -110,37 +110,64 @@ public function product_addons_params( array $params ): array { | |
*/ | ||
public function get_item_data( $addon_data, $addon, $cart_item ): array { | ||
$price = isset( $cart_item['addons_price_before_calc'] ) ? $cart_item['addons_price_before_calc'] : $addon['price']; | ||
$name = $addon['name']; | ||
|
||
if ( 0.0 === $addon['price'] ) { | ||
$name .= ''; | ||
} elseif ( 'percentage_based' === $addon['price_type'] && 0.0 === $price ) { | ||
$name .= ''; | ||
} elseif ( 'custom_price' === $addon['field_type'] ) { | ||
$name .= ' (' . wc_price( $addon['price'] ) . ')'; | ||
} elseif ( 'percentage_based' !== $addon['price_type'] && $addon['price'] && apply_filters( 'woocommerce_addons_add_price_to_name', '__return_true' ) ) { | ||
// Get our converted and tax adjusted price to put in the add on name. | ||
$price = $this->multi_currency->get_price( $addon['price'], 'product' ); | ||
if ( 'input_multiplier' === $addon['field_type'] ) { | ||
// Quantity/multiplier add on needs to be split, calculated, then multiplied by input value. | ||
$price = $this->multi_currency->get_price( $addon['price'] / $addon['value'], 'product' ) * $addon['value']; | ||
$value = $addon['value']; | ||
|
||
/* | ||
* 'woocommerce_addons_add_cart_price_to_value' | ||
* | ||
* Use this filter to display the price next to each selected add-on option. | ||
* By default, add-on prices show up only next to flat fee add-ons. | ||
* | ||
* @param boolean | ||
*/ | ||
$add_price_to_value = apply_filters( 'woocommerce_addons_add_cart_price_to_value', false, $cart_item ); | ||
jimjasson marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
if ( 0.0 === (float) $addon['price'] ) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This might be good enough here, but comparing floats for equality might cause subtle problems. Perhaps 0 is a special case here, but I think price can often be filtered and calculated, so maybe not totally safe? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the Product Add-Ons repo, we do this instead: https://github.com/woocommerce/woocommerce-product-addons/blob/trunk/includes/class-wc-product-addons-cart.php#L769, but Woo Payments repo doesn't allow non-strict comparisons and the |
||
$value .= ''; | ||
} elseif ( 'percentage_based' === $addon['price_type'] && 0.0 === (float) $price ) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here with comparing floats for equality. |
||
$value .= ''; | ||
} elseif ( 'custom_price' === $addon['field_type'] && $addon['price'] ) { | ||
if ( class_exists( 'WC_Product_Addons_Helper' ) ) { | ||
$addon_price = wc_price( \WC_Product_Addons_Helper::get_product_addon_price_for_display( $addon['price'], $cart_item['data'] ) ); | ||
/* translators: %1$s custom addon price in cart */ | ||
$value .= sprintf( _x( ' (%1$s)', 'custom price addon price in cart', 'woocommerce-payments' ), $addon_price ); | ||
$addon['display'] = $value; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: Should we add a comment indicating that if There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is also 1-1 identical to how Product Add-Ons handles the |
||
} | ||
} elseif ( 'flat_fee' === $addon['price_type'] && $addon['price'] ) { | ||
if ( class_exists( 'WC_Product_Addons_Helper' ) ) { | ||
$addon_price = $this->multi_currency->get_price( $addon['price'], 'product' ); | ||
if ( 'input_multiplier' === $addon['field_type'] ) { | ||
// Quantity/multiplier add on needs to be split, calculated, then multiplied by input value. | ||
$addon_price = $this->multi_currency->get_price( $addon['price'] / $addon['value'], 'product' ) * $addon['value']; | ||
} | ||
$addon_price = wc_price( \WC_Product_Addons_Helper::get_product_addon_price_for_display( $addon_price, $cart_item['data'] ) ); | ||
/* translators: %1$s flat fee addon price in order */ | ||
$value .= sprintf( _x( ' (+ %1$s)', 'flat fee addon price in cart', 'woocommerce-payments' ), $addon_price ); | ||
} | ||
if ( class_exists( '\WC_Product_Addons_Helper' ) ) { | ||
$price = \WC_Product_Addons_Helper::get_product_addon_price_for_display( $price, $cart_item['data'] ); | ||
$name .= ' (' . wc_price( $price ) . ')'; | ||
} elseif ( 'quantity_based' === $addon['price_type'] && $addon['price'] && $add_price_to_value ) { | ||
if ( class_exists( 'WC_Product_Addons_Helper' ) ) { | ||
$addon_price = $this->multi_currency->get_price( $addon['price'], 'product' ); | ||
if ( 'input_multiplier' === $addon['field_type'] ) { | ||
// Quantity/multiplier add on needs to be split, calculated, then multiplied by input value. | ||
$addon_price = $this->multi_currency->get_price( $addon['price'] / $addon['value'], 'product' ) * $addon['value']; | ||
} | ||
$addon_price = wc_price( \WC_Product_Addons_Helper::get_product_addon_price_for_display( $addon_price, $cart_item['data'] ) ); | ||
/* translators: %1$s addon price in order */ | ||
$value .= sprintf( _x( ' (%1$s)', 'quantity based addon price in cart', 'woocommerce-payments' ), $addon_price ); | ||
} | ||
} else { | ||
} elseif ( 'percentage_based' === $addon['price_type'] && $addon['price'] && $add_price_to_value ) { | ||
// Get the percentage cost in the currency in use, and set the meta data on the product that the value was converted. | ||
$_product = wc_get_product( $cart_item['product_id'] ); | ||
$price = $this->multi_currency->get_price( $price, 'product' ); | ||
$_product->set_price( $price * ( $addon['price'] / 100 ) ); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Out of scope for this PR, but interesting that we update the product price with percentage-based add-ons, but don't update it for quantity-based add-ons. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, this happens because the percentage amount needs to be calculated on top of the converted product price. Flat fees and quantity based add-ons are added on top of the product price and they don't need the product price to make any calculation about the actual add-on value. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for the explanation. |
||
$_product->update_meta_data( self::ADDONS_CONVERTED_META_KEY, 1 ); | ||
$name .= ' (' . WC()->cart->get_product_price( $_product ) . ')'; | ||
/* translators: %1$s addon price in order */ | ||
$value .= sprintf( _x( ' (%1$s)', 'percentage based addon price in cart', 'woocommerce-payments' ), WC()->cart->get_product_price( $_product ) ); | ||
} | ||
|
||
return [ | ||
'name' => $name, | ||
'value' => $addon['value'], | ||
'name' => $addon['name'], | ||
'value' => $value, | ||
'display' => isset( $addon['display'] ) ? $addon['display'] : '', | ||
]; | ||
} | ||
|
@@ -155,10 +182,14 @@ public function get_item_data( $addon_data, $addon, $cart_item ): array { | |
* @return array | ||
*/ | ||
public function update_product_price( $updated_prices, $cart_item, $prices ): array { | ||
$price = $this->multi_currency->get_price( $prices['price'], 'product' ); | ||
$regular_price = $this->multi_currency->get_price( $prices['regular_price'], 'product' ); | ||
$sale_price = $this->multi_currency->get_price( $prices['sale_price'], 'product' ); | ||
$quantity = $cart_item['quantity']; | ||
$price = $this->multi_currency->get_price( $prices['price'], 'product' ); | ||
$regular_price = $this->multi_currency->get_price( $prices['regular_price'], 'product' ); | ||
$sale_price = $this->multi_currency->get_price( $prices['sale_price'], 'product' ); | ||
$flat_fees = 0; | ||
$quantity = $cart_item['quantity']; | ||
$price_before_addons = $price; | ||
$regular_price_before_addons = $regular_price; | ||
$sale_price_before_addons = $sale_price; | ||
|
||
// TODO: Check compat with Smart Coupons. | ||
// Compatibility with Smart Coupons self declared gift amount purchase. | ||
|
@@ -191,14 +222,16 @@ public function update_product_price( $updated_prices, $cart_item, $prices ): ar | |
|
||
switch ( $addon['price_type'] ) { | ||
case 'percentage_based': | ||
$price += (float) ( $cart_item['data']->get_price( 'view' ) * ( $addon_price / 100 ) ); | ||
$regular_price += (float) ( $regular_price * ( $addon_price / 100 ) ); | ||
$sale_price += (float) ( $sale_price * ( $addon_price / 100 ) ); | ||
$price += (float) ( $price_before_addons * ( $addon_price / 100 ) ); | ||
$regular_price += (float) ( $regular_price_before_addons * ( $addon_price / 100 ) ); | ||
$sale_price += (float) ( $sale_price_before_addons * ( $addon_price / 100 ) ); | ||
break; | ||
case 'flat_fee': | ||
$price += (float) ( $addon_price / $quantity ); | ||
$regular_price += (float) ( $addon_price / $quantity ); | ||
$sale_price += (float) ( $addon_price / $quantity ); | ||
$flat_fee = $quantity > 0 ? (float) ( $addon_price / $quantity ) : 0; | ||
$price += $flat_fee; | ||
$regular_price += $flat_fee; | ||
$sale_price += $flat_fee; | ||
$flat_fees += $flat_fee; | ||
break; | ||
default: | ||
$price += (float) $addon_price; | ||
|
@@ -212,9 +245,10 @@ public function update_product_price( $updated_prices, $cart_item, $prices ): ar | |
$cart_item['data']->update_meta_data( self::ADDONS_CONVERTED_META_KEY, 1 ); | ||
|
||
return [ | ||
'price' => $price, | ||
'regular_price' => $regular_price, | ||
'sale_price' => $sale_price, | ||
'price' => $price, | ||
'regular_price' => $regular_price, | ||
'sale_price' => $sale_price, | ||
'addons_flat_fees_sum' => $flat_fees, | ||
]; | ||
} | ||
|
||
|
@@ -230,8 +264,17 @@ public function update_product_price( $updated_prices, $cart_item, $prices ): ar | |
*/ | ||
public function order_line_item_meta( array $meta_data, array $addon, \WC_Order_Item_Product $item, array $values ): array { | ||
|
||
$add_price_to_value = apply_filters( 'woocommerce_addons_add_order_price_to_value', false, $item ); | ||
|
||
$value = $addon['value']; | ||
|
||
// Pass the timestamp as the add-on value in order to save the timestamp to the DB. | ||
if ( isset( $addon['timestamp'] ) ) { | ||
$value = $addon['timestamp']; | ||
jimjasson marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
// If there is an add-on price, add the price of the add-on to the label name. | ||
if ( $addon['price'] && apply_filters( 'woocommerce_addons_add_price_to_name', true ) ) { | ||
if ( $addon['price'] && $add_price_to_value ) { | ||
$product = $item->get_product(); | ||
|
||
if ( 'percentage_based' === $addon['price_type'] && 0.0 !== (float) $product->get_price() ) { | ||
|
@@ -247,24 +290,30 @@ public function order_line_item_meta( array $meta_data, array $addon, \WC_Order_ | |
// Convert all others. | ||
$addon_price = $this->multi_currency->get_price( $addon['price'], 'product' ); | ||
} | ||
if ( class_exists( '\WC_Product_Addons_Helper' ) ) { | ||
$price = html_entity_decode( | ||
if ( class_exists( 'WC_Product_Addons_Helper' ) ) { | ||
$price = html_entity_decode( | ||
wp_strip_all_tags( wc_price( \WC_Product_Addons_Helper::get_product_addon_price_for_display( $addon_price, $values['data'] ) ) ), | ||
ENT_QUOTES, | ||
get_bloginfo( 'charset' ) | ||
); | ||
$addon['name'] .= ' (' . $price . ')'; | ||
} | ||
} | ||
|
||
if ( 'custom_price' === $addon['field_type'] ) { | ||
$addon['value'] = $addon['price']; | ||
if ( 'flat_fee' === $addon['price_type'] && $addon['price'] && $add_price_to_value ) { | ||
/* translators: %1$s flat fee addon price in order */ | ||
$value .= sprintf( _x( ' (+ %1$s)', 'flat fee addon price in order', 'woocommerce-payments' ), $price ); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We may want to consider handling things more gracefully when There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I thought about this, but couldn't find a case where the Right now, if this class is not defined, the the add-on value will be the same as the one set by Product Add-Ons, i.e. not converted by the selected currency. Would you prefer to have a different fallback here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Although it's unlikely, this is one scenario where this may happen:
Although this is possible, it's unlikely, so we can proceed without making modifications. |
||
} elseif ( ( 'quantity_based' === $addon['price_type'] || 'percentage_based' === $addon['price_type'] ) && $addon['price'] && $add_price_to_value ) { | ||
/* translators: %1$s addon price in order */ | ||
$value .= sprintf( _x( ' (%1$s)', 'addon price in order', 'woocommerce-payments' ), $price ); | ||
} elseif ( 'custom_price' === $addon['field_type'] ) { | ||
/* translators: %1$s custom addon price in order */ | ||
$value = sprintf( _x( ' (%1$s)', 'custom addon price in order', 'woocommerce-payments' ), $price ); | ||
} | ||
|
||
$meta_data['raw_price'] = $this->multi_currency->get_price( $addon['price'], 'product' ); | ||
} | ||
|
||
return [ | ||
'key' => $addon['name'], | ||
'value' => $addon['value'], | ||
]; | ||
$meta_data['value'] = $value; | ||
return $meta_data; | ||
} | ||
|
||
/** | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this method is very similar to \WC_Product_Addons_Cart::get_item_data, would it be worth it to add a comment to PAO to reflect changes in \WC_Product_Addons_Cart::get_item_data here in Woo Payments, too, so we can avoid things being out of sync in the future? (and similarly with other methods here that have counterparts in PAO)
Alternatively, we could refactor \WC_Product_Addons_Cart::get_item_data (or extract the calculation part out to a separate method) and call it from WooPayments, it's essentially a static method anyway, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added some comments here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks!