Automattic\WooCommerce\Internal\RestApi\Routes\V4\Refunds
DataUtils::build_refund_preview │ public │ WC 10.9.0 Build a refund preview showing authoritative totals and breakdowns.
Callers must invoke {@see validate_preview_line_items()} first — this method assumes inputs have been validated and throws on missing items.
Each line item must have 'line_item_id' and at least one of 'quantity' (positive int) or 'refund_total' (positive tax-inclusive float). When 'refund_total' is present and positive it is used directly; otherwise the total is computed from quantity via {@see compute_line_item_refund_total()}.
Method of the class: DataUtils{}
No Hooks.
Returns
Array. The structured preview response.
Usage
$DataUtils = new DataUtils();
$DataUtils->build_refund_preview( $order, $line_items ): array;
$order(WC_Order ) (required)
The order being previewed for refund.
$line_items(array) (required)
Line items. Each: array{line_item_id: int, quantity?: int, refund_total?: float}.
Changelog
DataUtils::build_refund_preview() DataUtils::build refund preview code
WC 11.0.1
public function build_refund_preview( WC_Order $order, array $line_items ): array {
$price_decimals = wc_get_price_decimals();
$sections = array(
'products' => array(
'items' => array(),
'subtotal' => 0.0,
'tax' => 0.0,
'total' => 0.0,
),
'shipping' => array(
'items' => array(),
'subtotal' => 0.0,
'tax' => 0.0,
'total' => 0.0,
),
'fees' => array(
'items' => array(),
'subtotal' => 0.0,
'tax' => 0.0,
'total' => 0.0,
),
);
foreach ( $line_items as $line_item ) {
$item = $order->get_item( $line_item['line_item_id'] );
if ( ! $item ) {
// Exception message is developer-facing only; both values are typed ints and the format is a literal string.
// phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
throw new \InvalidArgumentException( sprintf( 'Line item %d not found on order %d.', (int) $line_item['line_item_id'], (int) $order->get_id() ) );
}
/**
* Validated by validate_preview_line_items() upstream.
*
* @var WC_Order_Item_Product|WC_Order_Item_Shipping|WC_Order_Item_Fee $item
*/
// When the caller provides an explicit refund_total (partial-amount form) use it
// directly. The quantity-based form computes the tax-inclusive total from unit price.
// A non-zero check (not > 0) mirrors validate_preview_line_items(), which accepts a
// negative refund_total for a negative discount line and rejects a present-but-zero
// one before this method runs — so a signed value is honoured rather than falling
// through to the (possibly absent) quantity.
$refund_total_with_tax = isset( $line_item['refund_total'] ) && is_numeric( $line_item['refund_total'] ) && 0.0 !== (float) $line_item['refund_total']
? NumberUtil::round( (float) $line_item['refund_total'], $price_decimals )
: $this->compute_line_item_refund_total( $item, (int) $line_item['quantity'] );
// Split by the line's own stored total/tax ratio so the preview reflects what
// was actually charged and matches the split create stores (both call this).
$split = $this->split_inclusive_by_stored_ratio( $refund_total_with_tax, $item, $price_decimals );
$subtotal = $split['subtotal'];
$tax = $split['total_tax'];
$item_data = array(
'id' => $line_item['line_item_id'],
'quantity' => $line_item['quantity'] ?? null,
'subtotal' => wc_format_decimal( $subtotal, $price_decimals ),
'tax' => wc_format_decimal( $tax, $price_decimals ),
'total' => wc_format_decimal( $refund_total_with_tax, $price_decimals ),
);
$item_data['name'] = $item->get_name();
if ( $item instanceof WC_Order_Item_Product ) {
$variation_id = $item->get_variation_id();
$item_data['product_id'] = $variation_id > 0 ? $variation_id : $item->get_product_id();
$section_key = 'products';
} elseif ( $item instanceof WC_Order_Item_Shipping ) {
$section_key = 'shipping';
} else {
$section_key = 'fees';
}
$sections[ $section_key ]['items'][] = $item_data;
$sections[ $section_key ]['subtotal'] += $subtotal;
$sections[ $section_key ]['tax'] += $tax;
$sections[ $section_key ]['total'] += $refund_total_with_tax;
}
$format_section = function ( array $section ) use ( $price_decimals ): array {
return array(
'items' => $section['items'],
'subtotal' => wc_format_decimal( $section['subtotal'], $price_decimals ),
'tax' => wc_format_decimal( $section['tax'], $price_decimals ),
'total' => wc_format_decimal( $section['total'], $price_decimals ),
);
};
$grand_subtotal = $sections['products']['subtotal'] + $sections['shipping']['subtotal'] + $sections['fees']['subtotal'];
$grand_tax = $sections['products']['tax'] + $sections['shipping']['tax'] + $sections['fees']['tax'];
$grand_total = $sections['products']['total'] + $sections['shipping']['total'] + $sections['fees']['total'];
return array(
'breakdown' => array(
'products' => $format_section( $sections['products'] ),
'shipping' => $format_section( $sections['shipping'] ),
'fees' => $format_section( $sections['fees'] ),
),
'subtotal' => wc_format_decimal( $grand_subtotal, $price_decimals ),
'tax' => wc_format_decimal( $grand_tax, $price_decimals ),
'total' => wc_format_decimal( $grand_total, $price_decimals ),
'max_refundable' => wc_format_decimal( $order->get_remaining_refund_amount(), $price_decimals ),
);
}