@@ -1499,6 +1499,181 @@ public function test_preserve_order_taxes_rebases_total_when_shipping_changes()
14991499 $ order ->delete ( true );
15001500 }
15011501
1502+ /**
1503+ * Test that snapshot/restore round-trips every tax line when an order has more than one.
1504+ */
1505+ public function test_restore_order_taxes_restores_multiple_tax_lines () {
1506+ $ order = wc_create_order ();
1507+
1508+ $ state_tax = new WC_Order_Item_Tax ();
1509+ $ state_tax ->set_rate_id ( 6 );
1510+ $ state_tax ->set_rate_code ( 'US-CA-STATE-1 ' );
1511+ $ state_tax ->set_label ( 'CA State Tax ' );
1512+ $ state_tax ->set_rate_percent ( 6.0 );
1513+ $ state_tax ->set_tax_total ( '6.00 ' );
1514+ $ state_tax ->set_shipping_tax_total ( '0.00 ' );
1515+ $ order ->add_item ( $ state_tax );
1516+
1517+ $ county_tax = new WC_Order_Item_Tax ();
1518+ $ county_tax ->set_rate_id ( 7 );
1519+ $ county_tax ->set_rate_code ( 'US-CA-COUNTY-1 ' );
1520+ $ county_tax ->set_label ( 'LA County Tax ' );
1521+ $ county_tax ->set_rate_percent ( 2.25 );
1522+ $ county_tax ->set_tax_total ( '2.25 ' );
1523+ $ county_tax ->set_shipping_tax_total ( '0.00 ' );
1524+ $ order ->add_item ( $ county_tax );
1525+
1526+ $ order ->set_cart_tax ( '8.25 ' );
1527+ $ order ->set_total ( 8.25 );
1528+ $ order ->save ();
1529+
1530+ $ snapshot = $ this ->invoke_protected_method ( 'snapshot_order_taxes ' , array ( $ order ) );
1531+ $ this ->assertCount ( 2 , $ snapshot ['tax_lines ' ], 'Both tax lines should be snapshotted. ' );
1532+
1533+ foreach ( $ order ->get_taxes () as $ t ) {
1534+ $ order ->remove_item ( $ t ->get_id () );
1535+ }
1536+ $ order ->set_cart_tax ( 0 );
1537+ $ order ->set_total ( 0 );
1538+ $ order ->save ();
1539+
1540+ $ this ->invoke_protected_method ( 'restore_order_taxes ' , array ( $ order , $ snapshot ) );
1541+
1542+ $ restored = wc_get_order ( $ order ->get_id () );
1543+ $ this ->assertCount ( 2 , $ restored ->get_taxes (), 'Both tax lines should be restored. ' );
1544+
1545+ $ labels = array ();
1546+ foreach ( $ restored ->get_taxes () as $ line ) {
1547+ $ labels [ $ line ->get_rate_id () ] = $ line ->get_label ();
1548+ }
1549+ $ this ->assertEquals ( 'CA State Tax ' , $ labels [6 ], 'State tax label should be restored. ' );
1550+ $ this ->assertEquals ( 'LA County Tax ' , $ labels [7 ], 'County tax label should be restored. ' );
1551+ $ this ->assertEqualsWithDelta ( 8.25 , (float ) $ restored ->get_total_tax (), 0.001 , 'Combined tax should be restored. ' );
1552+
1553+ $ order ->delete ( true );
1554+ }
1555+
1556+ /**
1557+ * Test that a compound tax line's compound flag survives snapshot and restore.
1558+ */
1559+ public function test_restore_order_taxes_preserves_compound_flag () {
1560+ $ order = wc_create_order ();
1561+
1562+ $ tax_item = new WC_Order_Item_Tax ();
1563+ $ tax_item ->set_rate_id ( 8 );
1564+ $ tax_item ->set_rate_code ( 'CA-GST-1 ' );
1565+ $ tax_item ->set_label ( 'GST ' );
1566+ $ tax_item ->set_rate_percent ( 5.0 );
1567+ $ tax_item ->set_compound ( true );
1568+ $ tax_item ->set_tax_total ( '5.00 ' );
1569+ $ tax_item ->set_shipping_tax_total ( '0.00 ' );
1570+ $ order ->add_item ( $ tax_item );
1571+ $ order ->set_cart_tax ( '5.00 ' );
1572+ $ order ->set_total ( 5.00 );
1573+ $ order ->save ();
1574+
1575+ $ snapshot = $ this ->invoke_protected_method ( 'snapshot_order_taxes ' , array ( $ order ) );
1576+
1577+ foreach ( $ order ->get_taxes () as $ t ) {
1578+ $ order ->remove_item ( $ t ->get_id () );
1579+ }
1580+ $ order ->save ();
1581+
1582+ $ this ->invoke_protected_method ( 'restore_order_taxes ' , array ( $ order , $ snapshot ) );
1583+
1584+ $ restored = wc_get_order ( $ order ->get_id () );
1585+ $ taxes = $ restored ->get_taxes ();
1586+ $ line = reset ( $ taxes );
1587+ $ this ->assertTrue ( $ line ->get_compound (), 'Compound flag should survive snapshot and restore. ' );
1588+
1589+ $ order ->delete ( true );
1590+ }
1591+
1592+ /**
1593+ * Test that the order-level discount tax total is restored from the snapshot.
1594+ */
1595+ public function test_restore_order_taxes_restores_discount_tax () {
1596+ $ order = wc_create_order ();
1597+
1598+ $ tax_item = new WC_Order_Item_Tax ();
1599+ $ tax_item ->set_rate_id ( 6 );
1600+ $ tax_item ->set_label ( 'CA Tax ' );
1601+ $ tax_item ->set_tax_total ( '1.76 ' );
1602+ $ tax_item ->set_shipping_tax_total ( '0.00 ' );
1603+ $ order ->add_item ( $ tax_item );
1604+ $ order ->set_cart_tax ( '1.76 ' );
1605+ $ order ->set_discount_tax ( '0.50 ' );
1606+ $ order ->set_total ( 1.76 );
1607+ $ order ->save ();
1608+
1609+ $ snapshot = $ this ->invoke_protected_method ( 'snapshot_order_taxes ' , array ( $ order ) );
1610+ $ this ->assertEqualsWithDelta ( 0.50 , (float ) $ snapshot ['discount_tax ' ], 0.001 , 'Discount tax should be snapshotted. ' );
1611+
1612+ foreach ( $ order ->get_taxes () as $ t ) {
1613+ $ order ->remove_item ( $ t ->get_id () );
1614+ }
1615+ $ order ->set_discount_tax ( 0 );
1616+ $ order ->save ();
1617+
1618+ $ this ->invoke_protected_method ( 'restore_order_taxes ' , array ( $ order , $ snapshot ) );
1619+
1620+ $ restored = wc_get_order ( $ order ->get_id () );
1621+ $ this ->assertEqualsWithDelta ( 0.50 , (float ) $ restored ->get_discount_tax (), 0.001 , 'Discount tax should be restored. ' );
1622+
1623+ $ order ->delete ( true );
1624+ }
1625+
1626+ /**
1627+ * Test that a bare calculate_taxes() leaves the snapshot pending (documented gap:
1628+ * no restore fires on that hook), and that the pending snapshot is then restored by
1629+ * the handler a following calculate_totals() invokes, without double-applying.
1630+ */
1631+ public function test_preserve_order_taxes_pending_snapshot_restores_via_handler () {
1632+ remove_all_actions ( 'woocommerce_order_after_calculate_totals ' );
1633+ $ this ->set_private_property ( 'response_rate_ids ' , array () );
1634+
1635+ $ order = wc_create_order ();
1636+ $ tax_item = new WC_Order_Item_Tax ();
1637+ $ tax_item ->set_rate_id ( 6 );
1638+ $ tax_item ->set_label ( 'CA Tax ' );
1639+ $ tax_item ->set_tax_total ( '8.25 ' );
1640+ $ tax_item ->set_shipping_tax_total ( '0.00 ' );
1641+ $ order ->add_item ( $ tax_item );
1642+ $ order ->set_cart_tax ( '8.25 ' );
1643+ $ order ->set_total ( 8.25 );
1644+ $ order ->save ();
1645+
1646+ $ this ->integration ->preserve_order_taxes_on_recalculation ( array (), $ order );
1647+
1648+ $ this ->assertNotFalse (
1649+ has_action ( 'woocommerce_order_after_calculate_totals ' , array ( $ this ->integration , 'restore_order_taxes_after_recalculation ' ) ),
1650+ 'A single restore handler should be registered. '
1651+ );
1652+
1653+ // Simulate WC wiping the tax lines during the recalculation.
1654+ foreach ( $ order ->get_taxes () as $ t ) {
1655+ $ order ->remove_item ( $ t ->get_id () );
1656+ }
1657+ $ order ->set_cart_tax ( 0 );
1658+ $ order ->set_total ( 0 );
1659+ $ order ->save ();
1660+
1661+ $ this ->integration ->restore_order_taxes_after_recalculation ( true , $ order );
1662+
1663+ $ restored = wc_get_order ( $ order ->get_id () );
1664+ $ this ->assertCount ( 1 , $ restored ->get_taxes (), 'The recorded tax line should be restored. ' );
1665+ $ this ->assertEqualsWithDelta ( 8.25 , (float ) $ restored ->get_total_tax (), 0.001 , 'The recorded tax should be restored. ' );
1666+
1667+ // The snapshot is cleared once restored, so a second handler call is a no-op and
1668+ // cannot duplicate the tax or leak into a later recalculation.
1669+ $ this ->integration ->restore_order_taxes_after_recalculation ( true , wc_get_order ( $ order ->get_id () ) );
1670+ $ again = wc_get_order ( $ order ->get_id () );
1671+ $ this ->assertCount ( 1 , $ again ->get_taxes (), 'Restoring twice must not duplicate tax lines. ' );
1672+
1673+ remove_all_actions ( 'woocommerce_order_after_calculate_totals ' );
1674+ $ order ->delete ( true );
1675+ }
1676+
15021677 /**
15031678 * A tax calculation whose taxable amount is zero must not zero out the tax rate.
15041679 *
@@ -1524,7 +1699,7 @@ public function test_zero_amount_response_persists_real_itemized_rates() {
15241699
15251700 // taxable_amount and the top-level rate are 0, but the line item still
15261701 // carries the real jurisdiction rates (city 1.5%, county 1.25%, state 4.225%).
1527- $ line_item = (object ) array (
1702+ $ line_item = (object ) array (
15281703 'id ' => '351-regressionkey ' ,
15291704 'city_tax_rate ' => 0.015 ,
15301705 'county_tax_rate ' => 0.0125 ,
0 commit comments