get_method() ) ) { return; } $this->reconcile( $order, null, false, 'block checkout' ); } public function guard_classic( $order_id, $posted_data = array(), $order = null ) { unset( $posted_data ); if ( ! $order instanceof WC_Order ) { $order = wc_get_order( $order_id ); } $this->reconcile( $order, null, true, 'shortcode checkout' ); } public function guard_order_pay( $order ) { $posted = isset( $_POST['payment_method'] ) ? wc_clean( wp_unslash( $_POST['payment_method'] ) ) : ''; if ( $posted === '' ) { return; } $this->reconcile( $order, (string) $posted, true, 'order-pay retry' ); } public function reconcile( $order, $forced_method = null, $persist = false, $context = '' ) { if ( ! $order instanceof WC_Order ) { return false; } if ( ! apply_filters( 'aieo_payment_fee_guard_enabled', true, $order ) ) { return false; } $order_id = (int) $order->get_id(); if ( $order_id > 0 && isset( $this->seen[ $order_id ] ) ) { return false; } $s = $this->get_settings(); if ( $s['cod_enabled'] !== 'yes' ) { return false; } $label = self::cod_label( $s['cod_label'] ); $slug = trim( (string) $s['cod_gateway_slug'] ); if ( $label === '' || $slug === '' ) { return false; } if ( ! $this->is_reconcilable( $order ) ) { return false; } $method = ( $forced_method !== null ) ? (string) $forced_method : (string) $order->get_payment_method(); $is_cod = ( $method === $slug ); $taxable = ( $s['cod_taxable'] === 'yes' ); $expected = (float) $s['cod_fee']; $override = $this->fee_override_for_order( $order ); if ( $override !== null ) { $expected = (float) $override; } $matches = array(); foreach ( $order->get_items( 'fee' ) as $item_id => $item ) { if ( $this->label_matches( $item->get_name(), $label ) ) { $matches[ $item_id ] = $item; } } $changed = false; $action = ''; $before = 0.0; foreach ( $matches as $item ) { $before += (float) $item->get_total(); } if ( ! $is_cod || $expected <= 0 ) { foreach ( $matches as $item_id => $item ) { $order->remove_item( $item_id ); $changed = true; } $action = 'removed'; } else { $keep = null; foreach ( $matches as $item_id => $item ) { if ( $keep === null ) { $keep = $item; continue; } $order->remove_item( $item_id ); $changed = true; $action = 'deduplicated'; } if ( $keep === null ) { $fee = new WC_Order_Item_Fee(); $fee->set_name( $label ); $fee->set_total( wc_format_decimal( $expected, wc_get_price_decimals() ) ); $fee->set_tax_status( $taxable ? 'taxable' : 'none' ); $order->add_item( $fee ); $changed = true; $action = 'added'; } elseif ( abs( (float) $keep->get_total() - $expected ) >= 0.005 ) { $keep->set_total( wc_format_decimal( $expected, wc_get_price_decimals() ) ); $keep->set_tax_status( $taxable ? 'taxable' : 'none' ); $changed = true; $action = 'corrected'; } } if ( ! $changed ) { return false; } $order->calculate_totals( $taxable ); if ( $order_id > 0 ) { $this->seen[ $order_id ] = true; $order->add_order_note( sprintf( __( 'Payment Fee Guard %1$s "%2$s" (%3$s to %4$s). Payment method: %5$s. Context: %6$s.', 'ai-eshop-optimizer' ), $action, $label, wc_format_decimal( $before, wc_get_price_decimals() ), wc_format_decimal( $is_cod && $expected > 0 ? $expected : 0, wc_get_price_decimals() ), $method, $context !== '' ? $context : 'unknown' ) ); } $this->bump_stats( $action, $order_id, $method ); do_action( 'aieo_payment_fee_guard_corrected', $order, $action, $method, $context ); if ( $persist ) { $order->save(); } return true; } private function is_reconcilable( $order ) { if ( $order->get_type() !== 'shop_order' ) { return false; } if ( (float) $order->get_total_refunded() > 0 ) { return false; } $closed = array( 'completed', 'cancelled', 'refunded', 'failed' ); if ( in_array( $order->get_status(), $closed, true ) ) { return false; } return true; } private function label_matches( $candidate, $label ) { return mb_strtolower( trim( (string) $candidate ) ) === mb_strtolower( trim( (string) $label ) ); } private function fee_override_for_order( $order ) { $rates = get_option( self::OPTION_RATES_KEY, array() ); if ( ! is_array( $rates ) || empty( $rates ) ) { return null; } if ( ! class_exists( 'WC_Shipping_Zones' ) ) { return null; } $map = $this->rate_id_to_carrier_map(); if ( empty( $map ) ) { return null; } foreach ( $order->get_items( 'shipping' ) as $item ) { $method_id = (string) $item->get_method_id(); $instance_id = (string) $item->get_instance_id(); if ( $method_id === '' || $instance_id === '' ) { continue; } $base = $method_id . ':' . $instance_id; if ( isset( $map[ $base ] ) && isset( $rates[ $map[ $base ] ] ) ) { return (float) $rates[ $map[ $base ] ]; } } return null; } private function rate_id_to_carrier_map() { static $memo = null; if ( $memo !== null ) { return $memo; } $map = array(); $zones = WC_Shipping_Zones::get_zones(); $rest = WC_Shipping_Zones::get_zone( 0 ); if ( $rest ) { $zones[0] = array( 'shipping_methods' => $rest->get_shipping_methods( false ) ); } foreach ( $zones as $zone ) { $methods = isset( $zone['shipping_methods'] ) ? $zone['shipping_methods'] : array(); foreach ( $methods as $m ) { $iid = (int) ( method_exists( $m, 'get_instance_id' ) ? $m->get_instance_id() : 0 ); if ( $iid <= 0 ) { continue; } $mid = (string) $m->id; $title = method_exists( $m, 'get_title' ) ? (string) $m->get_title() : ''; if ( $title === '' ) { $title = $mid; } $map[ $mid . ':' . $iid ] = $mid . '|' . trim( $title ); } } $memo = $map; return $memo; } public static function cod_label( $stored ) { $label = trim( (string) $stored ); if ( '' === $label && did_action( 'init' ) ) { $label = __( 'Cash on delivery cost', 'ai-eshop-optimizer' ); } return $label; } private function get_settings() { if ( $this->settings_memo !== null ) { return $this->settings_memo; } $stored = get_option( self::OPTION_KEY, array() ); if ( ! is_array( $stored ) ) { $stored = array(); } $legacy = get_option( 'om_fs_settings', array() ); if ( ! is_array( $legacy ) ) { $legacy = array(); } $defaults = array( 'cod_enabled' => 'no', 'cod_gateway_slug' => 'cod', 'cod_fee' => 0, 'cod_label' => '', 'cod_taxable' => 'no', ); $this->settings_memo = array_merge( $defaults, array_merge( $legacy, $stored ) ); return $this->settings_memo; } private function bump_stats( $action, $order_id = 0, $method = '' ) { if ( $action === '' ) { return; } $stats = get_option( self::STATS_KEY, array() ); if ( ! is_array( $stats ) ) { $stats = array(); } $stats[ $action ] = isset( $stats[ $action ] ) ? ( (int) $stats[ $action ] ) + 1 : 1; $stats['total'] = isset( $stats['total'] ) ? ( (int) $stats['total'] ) + 1 : 1; $stats['last_gmt'] = gmdate( 'Y-m-d H:i:s' ) . ' UTC'; if ( (int) $order_id > 0 ) { $recent = isset( $stats['recent'] ) && is_array( $stats['recent'] ) ? $stats['recent'] : array(); array_unshift( $recent, array( 'order_id' => (int) $order_id, 'action' => (string) $action, 'method' => (string) $method, 'gmt' => gmdate( 'Y-m-d H:i:s' ), ) ); $stats['recent'] = array_slice( $recent, 0, 10 ); } update_option( self::STATS_KEY, $stats, false ); } } }