chore: initial snapshot of 2meet-data-optimizer-woocommerce-addon v0.1.0
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
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
<?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 ),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user