diff --git a/includes/class-tmdo-wc-orders-interceptor.php b/includes/class-tmdo-wc-orders-interceptor.php index 0652f19..cda335b 100644 --- a/includes/class-tmdo-wc-orders-interceptor.php +++ b/includes/class-tmdo-wc-orders-interceptor.php @@ -6,8 +6,9 @@ * `wp_wpdo_wc_commissions` table. HPOS-aware (uses `wc_get_order()`). * * Trigger: `woocommerce_order_status_changed` → on `processing`/`completed` - * read order's vendor meta (`_hp_vendor`, `_hp_commission`, etc.) - * and INSERT/UPDATE `wp_wpdo_wc_commissions`. + * read hivepress-marketplace's order meta (`hp_vendor`, + * `hp_commission_rate`, `hp_commission_fee`) and INSERT/UPDATE + * `wp_wpdo_wc_commissions`. * * Reads from order via `wc_get_order()` — works for both HPOS and legacy. * @@ -76,7 +77,7 @@ class TMDO_WC_Orders_Interceptor extends TMDO_Interceptor_Base { /** * INSERT/UPDATE wp_wpdo_wc_commissions for the given order. * - * Skips orders without a `_hp_vendor` meta — only marketplace orders count. + * Skips orders without an `hp_vendor` meta — only marketplace orders count. * * @param int $wc_order_id WooCommerce order ID. * @param string $status Order status. @@ -91,18 +92,30 @@ class TMDO_WC_Orders_Interceptor extends TMDO_Interceptor_Base { return; } - $vendor_id = (int) $order->get_meta( '_hp_vendor' ); - if ( $vendor_id <= 0 ) { + $vendor_post_id = (int) $order->get_meta( 'hp_vendor' ); + if ( $vendor_post_id <= 0 ) { // Not a marketplace order — skip. return; } - $listing_id = (int) $order->get_meta( '_hp_listing' ); - $subtotal = (float) ( $order->get_meta( '_hp_subtotal' ) ?: 0 ); - $commission = (float) ( $order->get_meta( '_hp_commission' ) ?: 0 ); - $vendor_payout = (float) ( $order->get_meta( '_hp_vendor_payout' ) ?: 0 ); - $commission_rate = (float) ( $order->get_meta( '_hp_commission_rate' ) ?: 0 ); - $hpos_enabled = class_exists( 'TMDO_WooCommerce' ) && TMDO_WooCommerce::is_hpos_enabled() ? 1 : 0; + // hivepress-marketplace stores the vendor's share (0.9 for a 10% commission) and + // a flat fee, and pays vendors net_total * share - fee (Marketplace::get_order_profit()). + $vendor_share = (float) $order->get_meta( 'hp_commission_rate' ); + $fee = (float) $order->get_meta( 'hp_commission_fee' ); + $subtotal = (float) $order->get_total() - (float) $order->get_total_refunded(); + if ( ! get_option( 'hp_vendor_include_taxes' ) ) { + $subtotal -= (float) $order->get_total_tax(); + } + $vendor_payout = max( 0.0, $subtotal * $vendor_share - $fee ); + $commission = $subtotal - $vendor_payout; + $commission_rate = round( ( 1 - $vendor_share ) * 100, 2 ); // Platform percentage, as set in Marketplace. + + // vendor_id holds the vendor's user ID (the admin screen resolves it with get_userdata()); + // the listing is the product's parent, as in Marketplace::get_product_listing(). + $vendor_id = (int) get_post_field( 'post_author', $vendor_post_id ); + $item = current( $order->get_items() ); + $listing_id = $item ? (int) get_post_field( 'post_parent', $item->get_product_id() ) : 0; + $hpos_enabled = class_exists( 'TMDO_WooCommerce' ) && TMDO_WooCommerce::is_hpos_enabled() ? 1 : 0; $table = TMDO_DB::table( 'wpdo_wc_commissions' ); $now = TMDO_DB::now(); diff --git a/tests/integration/WCOrdersInterceptorTest.php b/tests/integration/WCOrdersInterceptorTest.php new file mode 100644 index 0000000..09230ea --- /dev/null +++ b/tests/integration/WCOrdersInterceptorTest.php @@ -0,0 +1,160 @@ +product_id; + } + } +} + +if ( ! class_exists( 'TMDO_Test_WC_Order' ) ) { + final class TMDO_Test_WC_Order { + /** + * @param array $meta + */ + public function __construct( + private array $meta, + private float $total, + private float $tax, + private float $refunded, + private int $product_id + ) {} + + public function get_meta( string $key ) { + return $this->meta[ $key ] ?? ''; + } + + public function get_total(): float { + return $this->total; + } + + public function get_total_tax(): float { + return $this->tax; + } + + public function get_total_refunded(): float { + return $this->refunded; + } + + public function get_items(): array { + return array( new TMDO_Test_WC_Order_Item( $this->product_id ) ); + } + } +} + +if ( ! function_exists( 'wc_get_order' ) ) { + function wc_get_order( $order_id ) { + return $GLOBALS['_tmdo_test_orders'][ (int) $order_id ] ?? false; + } +} + +if ( ! function_exists( 'get_post_field' ) ) { + function get_post_field( string $field, $post ) { + return $GLOBALS['_tmdo_test_post_fields'][ (int) $post ][ $field ] ?? ''; + } +} + +/** + * Integration test: commission rows follow hivepress-marketplace's real order meta. + * + * Marketplace writes `hp_vendor` (vendor post ID), `hp_commission_rate` (vendor + * share, e.g. 0.9 for a 10% commission) and `hp_commission_fee` (flat fee), and + * pays vendors net_total * rate - fee (HivePress\Components\Marketplace::get_order_profit()). + */ +class WCOrdersInterceptorTest extends TestCase { + + private const ORDER_ID = 9001; + private const VENDOR_POST = 7; + private const VENDOR_USER = 42; + private const PRODUCT_ID = 900; + private const LISTING_ID = 555; + + public static function setUpBeforeClass(): void { + TMDO_Installer::install_v2_tables(); + } + + protected function setUp(): void { + global $wpdb; + $wpdb->prefix = 'wp_itest_'; + $wpdb->options = 'wp_itest_options'; + $wpdb->query( 'DELETE FROM `wp_itest_wpdo_wc_commissions` WHERE wc_order_id = ' . self::ORDER_ID ); + + delete_option( 'hp_vendor_include_taxes' ); + TMDO_Feature_Flags::set( 'wc_orders', 'dual_write' ); + + $GLOBALS['_tmdo_test_post_fields'] = array( + self::VENDOR_POST => array( 'post_author' => self::VENDOR_USER ), + self::PRODUCT_ID => array( 'post_parent' => self::LISTING_ID ), + ); + } + + protected function tearDown(): void { + TMDO_Feature_Flags::set( 'wc_orders', 'idle' ); + unset( $GLOBALS['_tmdo_test_orders'], $GLOBALS['_tmdo_test_post_fields'] ); + } + + private function place_order( array $meta, float $total, float $tax = 0.0, float $refunded = 0.0 ): void { + $GLOBALS['_tmdo_test_orders'][ self::ORDER_ID ] = new TMDO_Test_WC_Order( $meta, $total, $tax, $refunded, self::PRODUCT_ID ); + ( new TMDO_WC_Orders_Interceptor() )->action_order_status_changed( self::ORDER_ID, 'pending', 'processing' ); + } + + private function commission_row(): ?array { + global $wpdb; + return $wpdb->get_row( 'SELECT * FROM `wp_itest_wpdo_wc_commissions` WHERE wc_order_id = ' . self::ORDER_ID, ARRAY_A ); + } + + private function marketplace_meta(): array { + return array( + 'hp_vendor' => self::VENDOR_POST, + 'hp_commission_rate' => 0.9, + 'hp_commission_fee' => 5, + ); + } + + public function test_records_commission_from_marketplace_meta(): void { + $this->place_order( $this->marketplace_meta(), 110.0, 10.0 ); + + $row = $this->commission_row(); + + $this->assertNotNull( $row ); + $this->assertSame( self::VENDOR_USER, (int) $row['vendor_id'] ); + $this->assertSame( self::LISTING_ID, (int) $row['listing_id'] ); + $this->assertEqualsWithDelta( 100.0, (float) $row['subtotal'], 0.0001 ); + $this->assertEqualsWithDelta( 85.0, (float) $row['vendor_payout'], 0.0001 ); + $this->assertEqualsWithDelta( 15.0, (float) $row['commission'], 0.0001 ); + $this->assertEqualsWithDelta( 10.0, (float) $row['commission_rate'], 0.0001 ); + } + + public function test_includes_taxes_when_marketplace_setting_is_on(): void { + update_option( 'hp_vendor_include_taxes', '1' ); + + $this->place_order( $this->marketplace_meta(), 110.0, 10.0 ); + + $row = $this->commission_row(); + $this->assertEqualsWithDelta( 110.0, (float) $row['subtotal'], 0.0001 ); + $this->assertEqualsWithDelta( 94.0, (float) $row['vendor_payout'], 0.0001 ); + $this->assertEqualsWithDelta( 16.0, (float) $row['commission'], 0.0001 ); + } + + public function test_skips_orders_without_marketplace_vendor(): void { + $this->place_order( array(), 110.0 ); + + $this->assertNull( $this->commission_row() ); + } + + public function test_vendor_payout_never_goes_negative(): void { + $this->place_order( $this->marketplace_meta(), 3.0 ); + + $row = $this->commission_row(); + $this->assertEqualsWithDelta( 0.0, (float) $row['vendor_payout'], 0.0001 ); + $this->assertEqualsWithDelta( 3.0, (float) $row['commission'], 0.0001 ); + } +}