Skip to content

Commit 9894e7e

Browse files
layoutdclaude
andcommitted
Fix partial refunds to track already-refunded quantities and prevent over-refunding
When creating multiple refunds, the code was using original order quantities instead of accounting for items already refunded. This caused second refunds to exceed the original order quantities (e.g., 11 items refunded from an 8-item order). Now tracks refunded quantities per item and only refunds remaining quantities. All refund logic (full items, partial quantities, and fallback) now calculates remaining quantity = original - already_refunded before processing. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 0b3faa5 commit 9894e7e

File tree

1 file changed

+105
-27
lines changed

1 file changed

+105
-27
lines changed

includes/Generator/Order.php

Lines changed: 105 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,21 @@ protected static function create_refund( $order, $force_partial = false ) {
384384
$force_partial = true;
385385
}
386386

387+
// Calculate already refunded quantities per item
388+
$refunded_qty_by_item = array();
389+
foreach ( $existing_refunds as $existing_refund ) {
390+
foreach ( $existing_refund->get_items( array( 'line_item', 'fee' ) ) as $refund_item ) {
391+
$item_id = $refund_item->get_meta( '_refunded_item_id' );
392+
if ( ! $item_id ) {
393+
continue;
394+
}
395+
if ( ! isset( $refunded_qty_by_item[ $item_id ] ) ) {
396+
$refunded_qty_by_item[ $item_id ] = 0;
397+
}
398+
$refunded_qty_by_item[ $item_id ] += abs( $refund_item->get_quantity() );
399+
}
400+
}
401+
387402
// Refunds will be split evenly between partial and full (unless forced)
388403
$is_full_refund = $force_partial ? false : (bool) wp_rand( 0, 1 );
389404

@@ -392,18 +407,34 @@ protected static function create_refund( $order, $force_partial = false ) {
392407
if ( $is_full_refund ) {
393408
// Full refund - include all line items and fees
394409
foreach ( $order->get_items( array( 'line_item', 'fee' ) ) as $item_id => $item ) {
410+
// Calculate remaining quantity after previous refunds
411+
$original_qty = $item->get_quantity();
412+
$refunded_qty = isset( $refunded_qty_by_item[ $item_id ] ) ? $refunded_qty_by_item[ $item_id ] : 0;
413+
$remaining_qty = $original_qty - $refunded_qty;
414+
415+
// Skip if nothing left to refund
416+
if ( $remaining_qty <= 0 ) {
417+
continue;
418+
}
419+
395420
$taxes = $item->get_taxes();
396421
$refund_tax = array();
397422

398423
if ( ! empty( $taxes['total'] ) ) {
399424
foreach ( $taxes['total'] as $tax_id => $tax_amount ) {
400-
$refund_tax[ $tax_id ] = $tax_amount * -1;
425+
// Prorate tax based on remaining quantity
426+
$tax_per_unit = $tax_amount / $original_qty;
427+
$refund_tax[ $tax_id ] = ( $tax_per_unit * $remaining_qty ) * -1;
401428
}
402429
}
403430

431+
// Prorate the refund total based on remaining quantity
432+
$total_per_unit = $item->get_total() / $original_qty;
433+
$refund_total = $total_per_unit * $remaining_qty;
434+
404435
$line_items[ $item_id ] = array(
405-
'qty' => $item->get_quantity(),
406-
'refund_total' => $item->get_total() * -1,
436+
'qty' => $remaining_qty,
437+
'refund_total' => $refund_total * -1,
407438
'refund_tax' => $refund_tax,
408439
);
409440
}
@@ -428,37 +459,62 @@ protected static function create_refund( $order, $force_partial = false ) {
428459
foreach ( $items_to_refund as $index ) {
429460
$item = $items_array[ $index ];
430461
$item_id = $item->get_id();
462+
463+
// Calculate remaining quantity after previous refunds
464+
$original_qty = $item->get_quantity();
465+
$refunded_qty = isset( $refunded_qty_by_item[ $item_id ] ) ? $refunded_qty_by_item[ $item_id ] : 0;
466+
$remaining_qty = $original_qty - $refunded_qty;
467+
468+
// Skip if nothing left to refund
469+
if ( $remaining_qty <= 0 ) {
470+
continue;
471+
}
472+
431473
$taxes = $item->get_taxes();
432474
$refund_tax = array();
433475

434476
if ( ! empty( $taxes['total'] ) ) {
435477
foreach ( $taxes['total'] as $tax_id => $tax_amount ) {
436-
$refund_tax[ $tax_id ] = $tax_amount * -1;
478+
// Prorate tax based on remaining quantity
479+
$tax_per_unit = $tax_amount / $original_qty;
480+
$refund_tax[ $tax_id ] = ( $tax_per_unit * $remaining_qty ) * -1;
437481
}
438482
}
439483

484+
// Prorate the refund total based on remaining quantity
485+
$total_per_unit = $item->get_total() / $original_qty;
486+
$refund_total = $total_per_unit * $remaining_qty;
487+
440488
$line_items[ $item_id ] = array(
441-
'qty' => $item->get_quantity(),
442-
'refund_total' => $item->get_total() * -1,
489+
'qty' => $remaining_qty,
490+
'refund_total' => $refund_total * -1,
443491
'refund_tax' => $refund_tax,
444492
);
445493
}
446494
} else {
447495
// Refund partial quantities of items
448496
foreach ( $items as $item_id => $item ) {
449-
$quantity = $item->get_quantity();
497+
// Calculate remaining quantity after previous refunds
498+
$original_qty = $item->get_quantity();
499+
$refunded_qty = isset( $refunded_qty_by_item[ $item_id ] ) ? $refunded_qty_by_item[ $item_id ] : 0;
500+
$remaining_qty = $original_qty - $refunded_qty;
501+
502+
// Skip if nothing left to refund or if only 1 remaining
503+
if ( $remaining_qty <= 1 ) {
504+
continue;
505+
}
450506

451-
// Only refund line items with quantity > 1
452-
if ( 'line_item' === $item->get_type() && $quantity > 1 ) {
453-
// Refund between 1 and quantity-1 items
454-
$refund_qty = wp_rand( 1, $quantity - 1 );
455-
$refund_amount = ( $item->get_total() / $quantity ) * $refund_qty;
507+
// Only refund line items with remaining quantity > 1
508+
if ( 'line_item' === $item->get_type() ) {
509+
// Refund between 1 and remaining_qty-1 items
510+
$refund_qty = wp_rand( 1, $remaining_qty - 1 );
511+
$refund_amount = ( $item->get_total() / $original_qty ) * $refund_qty;
456512
$taxes = $item->get_taxes();
457513
$refund_tax = array();
458514

459515
if ( ! empty( $taxes['total'] ) ) {
460516
foreach ( $taxes['total'] as $tax_id => $tax_amount ) {
461-
$refund_tax[ $tax_id ] = ( $tax_amount / $quantity ) * $refund_qty * -1;
517+
$refund_tax[ $tax_id ] = ( $tax_amount / $original_qty ) * $refund_qty * -1;
462518
}
463519
}
464520

@@ -471,25 +527,47 @@ protected static function create_refund( $order, $force_partial = false ) {
471527
}
472528
}
473529

474-
// If no items were added (all quantities were 1), refund one complete item
530+
// If no items were added, refund one complete remaining item
475531
if ( empty( $line_items ) && count( $items ) > 0 ) {
532+
// Find an item with remaining quantity
476533
$items_array = array_values( $items );
477-
$item = $items_array[ array_rand( $items_array ) ];
478-
$item_id = $item->get_id();
479-
$taxes = $item->get_taxes();
480-
$refund_tax = array();
534+
shuffle( $items_array );
481535

482-
if ( ! empty( $taxes['total'] ) ) {
483-
foreach ( $taxes['total'] as $tax_id => $tax_amount ) {
484-
$refund_tax[ $tax_id ] = $tax_amount * -1;
536+
foreach ( $items_array as $item ) {
537+
$item_id = $item->get_id();
538+
539+
// Calculate remaining quantity after previous refunds
540+
$original_qty = $item->get_quantity();
541+
$refunded_qty = isset( $refunded_qty_by_item[ $item_id ] ) ? $refunded_qty_by_item[ $item_id ] : 0;
542+
$remaining_qty = $original_qty - $refunded_qty;
543+
544+
// Skip if nothing left to refund
545+
if ( $remaining_qty <= 0 ) {
546+
continue;
485547
}
486-
}
487548

488-
$line_items[ $item_id ] = array(
489-
'qty' => $item->get_quantity(),
490-
'refund_total' => $item->get_total() * -1,
491-
'refund_tax' => $refund_tax,
492-
);
549+
$taxes = $item->get_taxes();
550+
$refund_tax = array();
551+
552+
if ( ! empty( $taxes['total'] ) ) {
553+
foreach ( $taxes['total'] as $tax_id => $tax_amount ) {
554+
// Prorate tax based on remaining quantity
555+
$tax_per_unit = $tax_amount / $original_qty;
556+
$refund_tax[ $tax_id ] = ( $tax_per_unit * $remaining_qty ) * -1;
557+
}
558+
}
559+
560+
// Prorate the refund total based on remaining quantity
561+
$total_per_unit = $item->get_total() / $original_qty;
562+
$refund_total = $total_per_unit * $remaining_qty;
563+
564+
$line_items[ $item_id ] = array(
565+
'qty' => $remaining_qty,
566+
'refund_total' => $refund_total * -1,
567+
'refund_tax' => $refund_tax,
568+
);
569+
break; // Only refund one item
570+
}
493571
}
494572
}
495573
}

0 commit comments

Comments
 (0)