Skip to content

Commit 52e4398

Browse files
committed
fix(api-client): normalize fractional WC quantities below 1 to a single unit
WooCommerce allows fractional cart/order quantities (e.g. 0.3, via plugins like Measurement Price Calculator), which ItemFactory truncated via (int) cast to 0. PayPal's API requires quantity >= 1 and rejects 0 with INVALID_PARAMETER_SYNTAX, failing checkout entirely. For quantities between 0 and 1, normalize to a single PayPal unit priced at the full line subtotal (and re-derive unit tax accordingly) instead of truncating the quantity away. Quantities >= 1 are unaffected.
1 parent 96084e2 commit 52e4398

1 file changed

Lines changed: 29 additions & 10 deletions

File tree

modules/ppcp-api-client/src/Factory/ItemFactory.php

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,22 @@ function ( array $item ): Item {
5757
$product = $item['data'];
5858
$cart_item_key = $item['key'] ?? null;
5959

60-
$quantity = (int) $item['quantity'];
61-
$image = wp_get_attachment_image_src( (int) $product->get_image_id(), 'full' );
60+
$wc_quantity = (float) $item['quantity'];
61+
/**
62+
* PayPal requires an integer quantity of at least 1. WooCommerce allows
63+
* fractional quantities (e.g. 0.3, via plugins like Measurement Price
64+
* Calculator), which would otherwise truncate to 0 and be rejected by
65+
* the PayPal API. Normalize such lines to a single unit priced at the
66+
* full line subtotal, instead of truncating the quantity to 0.
67+
*/
68+
$is_fractional_unit = $wc_quantity > 0 && $wc_quantity < 1;
69+
$quantity = $is_fractional_unit ? 1 : (int) $wc_quantity;
70+
$image = wp_get_attachment_image_src( (int) $product->get_image_id(), 'full' );
6271

63-
$price = (float) $item['line_subtotal'] / (float) $item['quantity'];
64-
$line_tax = isset( $item['line_tax'] ) ? (float) $item['line_tax'] : 0.0;
65-
$unit_tax = $quantity > 0 ? $line_tax / (float) $quantity : 0.0;
72+
$line_subtotal = (float) $item['line_subtotal'];
73+
$price = $is_fractional_unit ? $line_subtotal : $line_subtotal / $wc_quantity;
74+
$line_tax = isset( $item['line_tax'] ) ? (float) $item['line_tax'] : 0.0;
75+
$unit_tax = $is_fractional_unit ? $line_tax : ( $quantity > 0 ? $line_tax / (float) $quantity : 0.0 );
6676

6777
return new Item(
6878
$this->prepare_item_string( $product->get_name() ),
@@ -137,14 +147,23 @@ function ( \WC_Order_Item_Fee $item ) use ( $order ): Item {
137147
* @return Item
138148
*/
139149
private function from_wc_order_line_item( \WC_Order_Item_Product $item, \WC_Order $order ): Item {
140-
$product = $item->get_product();
141-
$currency = $order->get_currency();
142-
$quantity = (int) $item->get_quantity();
143-
$price_without_tax = (float) $order->get_item_subtotal( $item, false );
150+
$product = $item->get_product();
151+
$currency = $order->get_currency();
152+
$wc_quantity = (float) $item->get_quantity();
153+
/**
154+
* PayPal requires an integer quantity of at least 1. WooCommerce allows
155+
* fractional quantities (e.g. 0.3, via plugins like Measurement Price
156+
* Calculator), which would otherwise truncate to 0 and be rejected by
157+
* the PayPal API. Normalize such lines to a single unit priced at the
158+
* full line subtotal, instead of truncating the quantity to 0.
159+
*/
160+
$is_fractional_unit = $wc_quantity > 0 && $wc_quantity < 1;
161+
$quantity = $is_fractional_unit ? 1 : (int) $wc_quantity;
162+
$price_without_tax = $is_fractional_unit ? (float) $item->get_subtotal() : (float) $order->get_item_subtotal( $item, false );
144163
$price_without_tax_rounded = round( $price_without_tax, 2 );
145164
$image = $product instanceof WC_Product ? wp_get_attachment_image_src( (int) $product->get_image_id(), 'full' ) : '';
146165
$line_tax = (float) $item->get_total_tax();
147-
$unit_tax = $quantity > 0 ? $line_tax / (float) $quantity : 0.0;
166+
$unit_tax = $is_fractional_unit ? $line_tax : ( $quantity > 0 ? $line_tax / (float) $quantity : 0.0 );
148167

149168
return new Item(
150169
$this->prepare_item_string( $item->get_name() ),

0 commit comments

Comments
 (0)