47288db826
Baseline before backporting wp-data-optimizer v3.0.1-v3.4.6. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TbG1keQQ7XBa7qMQY16KCY
171 lines
5.8 KiB
PHP
171 lines
5.8 KiB
PHP
<?php
|
|
/**
|
|
* TMDO_WC_Orders_Interceptor — vendor commission tracking for WC marketplaces.
|
|
*
|
|
* V2.1.0 rewrite: replaces legacy `hpct_wc_orders` write path with own
|
|
* `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`.
|
|
*
|
|
* Reads from order via `wc_get_order()` — works for both HPOS and legacy.
|
|
*
|
|
* @package WP_Data_Optimizer
|
|
* @since 1.0.0 (legacy hpct_wc_orders write path)
|
|
* @since 2.1.0 (wp_wpdo_wc_commissions custom table; HPOS-aware)
|
|
*/
|
|
|
|
if ( ! defined( 'ABSPATH' ) ) {
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* WC Orders interceptor — modern v2 model.
|
|
*
|
|
* Operates on `wp_wpdo_wc_commissions` (own custom table). HPCT legacy code removed.
|
|
*/
|
|
class TMDO_WC_Orders_Interceptor extends TMDO_Interceptor_Base {
|
|
|
|
/**
|
|
* Module identifier.
|
|
*
|
|
* @var string
|
|
*/
|
|
protected string $module = 'wc_orders';
|
|
|
|
/**
|
|
* Order statuses that trigger commission recording.
|
|
*
|
|
* @var array<string>
|
|
*/
|
|
private const TRACKED_STATUSES = array( 'completed', 'processing' );
|
|
|
|
/**
|
|
* Registers WordPress hooks for this interceptor.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function register_hooks(): void {
|
|
add_action( 'woocommerce_order_status_changed', array( $this, 'action_order_status_changed' ), 10, 3 );
|
|
}
|
|
|
|
/**
|
|
* Sync order data when WC status changes to a tracked state.
|
|
*
|
|
* @param int $order_id Order ID.
|
|
* @param string $old_status Old status (unused).
|
|
* @param string $new_status New status.
|
|
* @return void
|
|
*/
|
|
public function action_order_status_changed( int $order_id, string $old_status, string $new_status ): void { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
|
|
if ( ! $this->is_active() ) {
|
|
return;
|
|
}
|
|
if ( ! in_array( $new_status, self::TRACKED_STATUSES, true ) ) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
$this->sync_commission( $order_id, $new_status );
|
|
} catch ( \Throwable $e ) {
|
|
TMDO_Logger::error( $this->module, 'woocommerce_order_status_changed', $e->getMessage() );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* INSERT/UPDATE wp_wpdo_wc_commissions for the given order.
|
|
*
|
|
* Skips orders without a `_hp_vendor` meta — only marketplace orders count.
|
|
*
|
|
* @param int $wc_order_id WooCommerce order ID.
|
|
* @param string $status Order status.
|
|
* @return void
|
|
*/
|
|
private function sync_commission( int $wc_order_id, string $status ): void {
|
|
if ( ! function_exists( 'wc_get_order' ) ) {
|
|
return;
|
|
}
|
|
$order = wc_get_order( $wc_order_id );
|
|
if ( ! $order ) {
|
|
return;
|
|
}
|
|
|
|
$vendor_id = (int) $order->get_meta( '_hp_vendor' );
|
|
if ( $vendor_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;
|
|
|
|
$table = TMDO_DB::table( 'wpdo_wc_commissions' );
|
|
$now = TMDO_DB::now();
|
|
|
|
// v2.1.2 race-condition fix: atomic upsert via UNIQUE KEY ui_order_vendor.
|
|
// Replaces SELECT-then-INSERT/UPDATE which could race two concurrent
|
|
// status-change events (e.g. gateway IPN + admin click) and trigger
|
|
// duplicate-key DB errors. Single round-trip; on conflict only updates
|
|
// the mutable columns (status / updated_at), preserves financials.
|
|
TMDO_DB::upsert(
|
|
$table,
|
|
array(
|
|
'wc_order_id' => $wc_order_id,
|
|
'vendor_id' => $vendor_id,
|
|
'listing_id' => $listing_id,
|
|
'subtotal' => $subtotal,
|
|
'commission' => $commission,
|
|
'vendor_payout' => $vendor_payout,
|
|
'commission_rate' => $commission_rate,
|
|
'status' => $status,
|
|
'hpos_enabled' => $hpos_enabled,
|
|
'created_at' => $now,
|
|
'updated_at' => $now,
|
|
),
|
|
array( 'status', 'updated_at' ), // columns to update on conflict.
|
|
array( 'wc_order_id', 'vendor_id' ), // composite unique key.
|
|
array( '%d', '%d', '%d', '%f', '%f', '%f', '%f', '%s', '%d', '%s', '%s' )
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Aggregate commission summary for a vendor.
|
|
*
|
|
* @param int $vendor_id Vendor user ID.
|
|
* @param string $status Optional status filter ('processing' / 'completed' / 'all').
|
|
* @return array{total_subtotal:float, total_commission:float, total_payout:float, order_count:int}
|
|
*/
|
|
public static function vendor_summary( int $vendor_id, string $status = 'all' ): array {
|
|
global $wpdb;
|
|
$table = TMDO_DB::table( 'wpdo_wc_commissions' );
|
|
|
|
$where = $wpdb->prepare( ' WHERE vendor_id = %d ', $vendor_id );
|
|
if ( 'all' !== $status ) {
|
|
$where .= $wpdb->prepare( ' AND status = %s ', $status );
|
|
}
|
|
|
|
// phpcs:ignore WordPress.DB.DirectDatabaseQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQL.NotPrepared -- {$table} via TMDO_DB::table(); {$where} is a pre-validated SQL fragment.
|
|
$row = $wpdb->get_row(
|
|
"SELECT
|
|
SUM(subtotal) AS total_subtotal,
|
|
SUM(commission) AS total_commission,
|
|
SUM(vendor_payout) AS total_payout,
|
|
COUNT(*) AS order_count
|
|
FROM `{$table}` {$where}", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- {$table} via TMDO_DB::table(); {$where} is a pre-validated SQL fragment.
|
|
ARRAY_A
|
|
);
|
|
|
|
return array(
|
|
'total_subtotal' => (float) ( $row['total_subtotal'] ?? 0 ),
|
|
'total_commission' => (float) ( $row['total_commission'] ?? 0 ),
|
|
'total_payout' => (float) ( $row['total_payout'] ?? 0 ),
|
|
'order_count' => (int) ( $row['order_count'] ?? 0 ),
|
|
);
|
|
}
|
|
}
|