feat(upsell): apply real cart discounts and track performance

This commit is contained in:
2026-05-08 19:16:01 +03:30
parent 8345e94a1b
commit fd9d29a0ee
19 changed files with 747 additions and 206 deletions

View File

@@ -12,6 +12,21 @@ $upsellRepository = new UpsellRepository();
$sodino_upsell_service = new UpsellService($upsellRepository);
add_action('woocommerce_before_cart', 'sodino_render_upsell_suggestions');
add_action('wp_enqueue_scripts', 'sodino_enqueue_upsell_assets');
add_filter('woocommerce_add_cart_item_data', 'sodino_add_upsell_cart_item_data', 10, 4);
add_filter('woocommerce_get_cart_item_from_session', 'sodino_restore_upsell_cart_item_data', 10, 2);
add_action('woocommerce_before_calculate_totals', 'sodino_apply_upsell_cart_prices', 20);
add_filter('woocommerce_get_item_data', 'sodino_display_upsell_cart_item_data', 10, 2);
add_action('woocommerce_checkout_create_order_line_item', 'sodino_add_upsell_order_item_meta', 10, 4);
add_action('woocommerce_add_to_cart', 'sodino_track_upsell_conversion', 20, 6);
function sodino_enqueue_upsell_assets() {
if (is_admin()) {
return;
}
wp_enqueue_style('sodino-upsell-frontend', plugin_dir_url(__FILE__) . '../css/upsell-frontend.css', [], SODINO_VERSION);
}
function sodino_render_upsell_suggestions() {
if (is_admin() || !is_cart()) {
@@ -41,19 +56,19 @@ function sodino_render_upsell_suggestions() {
return;
}
echo '<div class="sodino-upsell-panel bg-white rounded-2xl border border-gray-200 p-6 mb-8 shadow-sm">';
echo '<div class="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">';
echo '<div>';
echo '<p class="text-sm font-semibold text-blue-600">' . esc_html__('پیشنهاد ویژه آپسل', 'sodino') . '</p>';
echo '<h2 class="mt-2 text-xl font-bold text-gray-900">' . esc_html__('این محصول را همراه خرید خود با تخفیف ویژه دریافت کنید', 'sodino') . '</h2>';
echo '<section class="sodino-upsell-panel" aria-label="' . esc_attr__('پیشنهادهای ویژه سودینو', 'sodino') . '">';
echo '<div class="sodino-upsell-header">';
echo '<div>';
echo '<p class="sodino-upsell-eyebrow">' . esc_html__('پیشنهاد ویژه', 'sodino') . '</p>';
echo '<h2>' . esc_html__('این پیشنهادها را با تخفیف به سبد خود اضافه کنید', 'sodino') . '</h2>';
echo '</div>';
echo '<span class="inline-flex items-center rounded-full bg-blue-50 px-3 py-1 text-sm font-medium text-blue-700">' . count($upsells) . ' ' . esc_html__('پیشنهاد فعال', 'sodino') . '</span>';
echo '<span class="sodino-upsell-count">' . esc_html(sprintf(__('%d پیشنهاد فعال', 'sodino'), count($upsells))) . '</span>';
echo '</div>';
echo '<div class="mt-6 grid gap-4 lg:grid-cols-'.min(2, count($upsells)).'">';
echo '<div class="sodino-upsell-grid">';
foreach ($upsells as $upsell) {
$product = wc_get_product($upsell->target_product_id);
if (!$product) {
if (!$product || !$product->is_purchasable() || !$product->is_in_stock() || !$product->is_type(['simple', 'variation'])) {
continue;
}
@@ -61,31 +76,161 @@ function sodino_render_upsell_suggestions() {
$originalPrice = floatval($product->get_price());
$priceHtml = wc_price($discountedPrice);
if ($discountedPrice < $originalPrice) {
$priceHtml .= ' <span class="mr-2 text-sm text-gray-500 line-through">' . wc_price($originalPrice) . '</span>';
$priceHtml .= ' <del>' . wc_price($originalPrice) . '</del>';
}
$addToCartUrl = esc_url(add_query_arg('add-to-cart', $product->get_id(), wc_get_cart_url()));
$image = $product->get_image('woocommerce_thumbnail', ['class' => 'h-20 w-20 rounded-xl object-cover']);
$addToCartUrl = sodino_get_upsell_add_to_cart_url($product, $upsell);
$image = $product->get_image('woocommerce_thumbnail', ['class' => 'sodino-upsell-image']);
$savings = max(0, $originalPrice - $discountedPrice);
$sodino_upsell_service->incrementImpression($upsell->id);
echo '<div class="rounded-2xl border border-gray-200 p-5 bg-gray-50">';
echo '<div class="flex gap-4">';
echo '<div class="flex-shrink-0">' . $image . '</div>';
echo '<div class="flex-1">';
echo '<p class="text-sm font-medium text-gray-700">' . esc_html($upsell->title) . '</p>';
echo '<h3 class="mt-2 text-lg font-semibold text-gray-900">' . esc_html($product->get_name()) . '</h3>';
echo '<div class="mt-3 flex items-center gap-3">';
echo '<span class="rounded-full bg-green-50 px-3 py-1 text-sm font-medium text-green-700">' . esc_html($sodino_upsell_service->getDiscountLabel($upsell)) . '</span>';
echo '<span class="text-sm text-gray-600">' . esc_html($sodino_upsell_service->getTriggerLabel($upsell)) . '</span>';
echo '</div>';
echo '</div>';
echo '</div>';
echo '<div class="mt-5 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">';
echo '<div class="text-lg font-semibold text-gray-900">' . $priceHtml . '</div>';
echo '<a href="' . $addToCartUrl . '" class="inline-flex items-center justify-center rounded-full bg-blue-600 px-5 py-3 text-sm font-semibold text-white hover:bg-blue-700">' . esc_html__('افزودن به سبد', 'sodino') . '</a>';
echo '<article class="sodino-upsell-card">';
echo '<div class="sodino-upsell-media">' . $image . '</div>';
echo '<div class="sodino-upsell-body">';
echo '<div class="sodino-upsell-meta">';
echo '<span>' . esc_html($sodino_upsell_service->getDiscountLabel($upsell)) . '</span>';
echo '<span>' . esc_html($sodino_upsell_service->getTriggerLabel($upsell)) . '</span>';
echo '</div>';
echo '<p class="sodino-upsell-title">' . esc_html($upsell->title) . '</p>';
echo '<h3>' . esc_html($product->get_name()) . '</h3>';
echo '<div class="sodino-upsell-footer">';
echo '<div class="sodino-upsell-price">' . $priceHtml;
if ($savings > 0) {
echo '<small>' . esc_html(sprintf(__('صرفه‌جویی: %s', 'sodino'), wp_strip_all_tags(wc_price($savings)))) . '</small>';
}
echo '</div>';
echo '<a href="' . esc_url($addToCartUrl) . '" class="sodino-upsell-button">' . esc_html__('افزودن با تخفیف', 'sodino') . '</a>';
echo '</div></div>';
echo '</article>';
}
echo '</div>';
echo '</div>';
echo '</section>';
}
function sodino_get_upsell_add_to_cart_url($product, $upsell) {
return add_query_arg(
[
'add-to-cart' => $product->get_id(),
'quantity' => 1,
'sodino_upsell_id' => $upsell->id,
'sodino_upsell_nonce' => wp_create_nonce('sodino_apply_upsell_' . $upsell->id),
],
wc_get_cart_url()
);
}
function sodino_add_upsell_cart_item_data($cart_item_data, $product_id, $variation_id, $quantity) {
if (empty($_REQUEST['sodino_upsell_id'])) {
return $cart_item_data;
}
$upsellId = absint(wp_unslash($_REQUEST['sodino_upsell_id']));
$nonce = sanitize_text_field(wp_unslash($_REQUEST['sodino_upsell_nonce'] ?? ''));
if (!$upsellId || !wp_verify_nonce($nonce, 'sodino_apply_upsell_' . $upsellId)) {
return $cart_item_data;
}
global $sodino_upsell_service;
if (!isset($sodino_upsell_service) || !WC()->cart) {
return $cart_item_data;
}
$upsell = $sodino_upsell_service->getById($upsellId);
if (
!$sodino_upsell_service->canApplyToProduct($upsell, $product_id, $variation_id)
|| !$sodino_upsell_service->isValidForCart($upsell, WC()->cart)
) {
return $cart_item_data;
}
$product = wc_get_product($variation_id ?: $product_id);
if (!$product) {
return $cart_item_data;
}
$originalPrice = floatval($product->get_price());
$discountedPrice = $sodino_upsell_service->calculateDiscountedPrice($originalPrice, $upsell);
$cart_item_data['sodino_upsell'] = [
'id' => (int) $upsell->id,
'title' => sanitize_text_field($upsell->title),
'discount_type' => sanitize_key($upsell->discount_type),
'discount_value' => floatval($upsell->discount_value),
'original_price' => $originalPrice,
'discounted_price' => $discountedPrice,
];
$cart_item_data['sodino_upsell_key'] = md5(wp_json_encode($cart_item_data['sodino_upsell']) . microtime(true));
return $cart_item_data;
}
function sodino_restore_upsell_cart_item_data($cart_item, $values) {
if (!empty($values['sodino_upsell'])) {
$cart_item['sodino_upsell'] = $values['sodino_upsell'];
$cart_item['sodino_upsell_key'] = $values['sodino_upsell_key'] ?? '';
}
return $cart_item;
}
function sodino_apply_upsell_cart_prices($cart) {
if (is_admin() && !defined('DOING_AJAX')) {
return;
}
if (!$cart) {
return;
}
foreach ($cart->get_cart() as $cartItem) {
if (empty($cartItem['sodino_upsell']['discounted_price']) || empty($cartItem['data'])) {
continue;
}
$cartItem['data']->set_price(max(0, floatval($cartItem['sodino_upsell']['discounted_price'])));
}
}
function sodino_display_upsell_cart_item_data($item_data, $cart_item) {
if (empty($cart_item['sodino_upsell'])) {
return $item_data;
}
$upsell = $cart_item['sodino_upsell'];
$item_data[] = [
'key' => __('پیشنهاد سودینو', 'sodino'),
'value' => esc_html($upsell['title']),
];
if (!empty($upsell['original_price']) && floatval($upsell['discounted_price']) < floatval($upsell['original_price'])) {
$item_data[] = [
'key' => __('تخفیف آپسل', 'sodino'),
'value' => wp_kses_post(wc_price(floatval($upsell['original_price']) - floatval($upsell['discounted_price']))),
];
}
return $item_data;
}
function sodino_add_upsell_order_item_meta($item, $cart_item_key, $values, $order) {
if (empty($values['sodino_upsell'])) {
return;
}
$upsell = $values['sodino_upsell'];
$item->add_meta_data('_sodino_upsell_id', (int) $upsell['id'], true);
$item->add_meta_data(__('پیشنهاد سودینو', 'sodino'), sanitize_text_field($upsell['title']), true);
$item->add_meta_data(__('قیمت اصلی آپسل', 'sodino'), wc_price(floatval($upsell['original_price'])), true);
$item->add_meta_data(__('قیمت تخفیفی آپسل', 'sodino'), wc_price(floatval($upsell['discounted_price'])), true);
}
function sodino_track_upsell_conversion($cart_item_key, $product_id, $quantity, $variation_id, $variation, $cart_item_data) {
if (empty($cart_item_data['sodino_upsell']['id'])) {
return;
}
global $sodino_upsell_service;
if (isset($sodino_upsell_service)) {
$sodino_upsell_service->incrementConversion((int) $cart_item_data['sodino_upsell']['id']);
}
}