Files
2meet-data-optimizer-woocom…/includes/class-tmdo-wc-orders-interceptor.php
T
wpdev 2f9ed57390 chore(wc): 同步 A v3.4.6 的 WooCommerce 整合 + TablePrefix header(F4/F5)
4 個整合檔(woocommerce / wc-orders-interceptor / wc-term-count-filter /
admin-wc)對齊 A v3.4.6;主檔補 TablePrefix header。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TbG1keQQ7XBa7qMQY16KCY
2026-07-31 06:07:39 +08:00

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 TMDO
* @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 ),
);
}
}