yoone-wc-subscriptions/includes/frontend/class-yoone-subscriptions-f...

186 lines
8.6 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/**
* 前端:产品页订阅选项渲染、加入购物车的校验与价格计算。
*/
defined('ABSPATH') || exit;
class Yoone_Subscriptions_Frontend {
protected static $instance = null;
public static function instance() {
if (null === self::$instance) self::$instance = new self();
return self::$instance;
}
private function __construct() {
// 在简单产品的 add-to-cart 区域前渲染订阅选项
add_action('woocommerce_before_add_to_cart_button', array($this, 'render_subscription_options'));
// 校验与存储购物车项数据
add_filter('woocommerce_add_to_cart_validation', array($this, 'validate_add_to_cart'), 10, 6);
add_filter('woocommerce_add_cart_item_data', array($this, 'add_cart_item_data'), 10, 3);
// 展示购物车/订单中的订阅摘要
add_filter('woocommerce_get_item_data', array($this, 'display_item_data'), 10, 2);
// 动态设置行项目价格
add_action('woocommerce_before_calculate_totals', array($this, 'adjust_subscription_price'), 20, 1);
}
/**
* 渲染订阅选择 UI产品页
*/
public function render_subscription_options() {
global $product;
if (! $product || ! is_a($product, 'WC_Product')) return;
$cfg = Yoone_Subscriptions::get_config($product);
if (! $cfg['enabled']) return; // 未开启订阅
// 前端样式/脚本
wp_enqueue_style('yoone-subs-frontend');
wp_enqueue_script('yoone-subs-frontend');
$regular_price = floatval($product->get_price());
$sub_price = $cfg['price'] > 0 ? $cfg['price'] : $regular_price;
$discount = ($regular_price > 0 && $sub_price < $regular_price) ? (1 - $sub_price / $regular_price) * 100.0 : 0.0;
// 简单模板输出(不使用专门模板文件,保持轻量)
echo '<div class="yoone-subs-block">';
echo '<h3>' . esc_html__('订阅选项', 'yoone-subscriptions') . '</h3>';
// 一次性 vs 订阅
if ($cfg['allow_onetime']) {
echo '<p><label><input type="radio" name="yoone_sub_purchase_mode" value="onetime" checked> ' . esc_html__('一次性购买', 'yoone-subscriptions') . '</label></p>';
echo '<p><label><input type="radio" name="yoone_sub_purchase_mode" value="subscribe"> ' . esc_html__('订阅购买', 'yoone-subscriptions') . '</label></p>';
} else {
echo '<input type="hidden" name="yoone_sub_purchase_mode" value="subscribe" />';
}
// 周期选择
echo '<p class="yoone-subs-row">'
. '<label>' . esc_html__('订阅周期', 'yoone-subscriptions') . '</label> '
. '<select name="yoone_sub_period">'
. '<option value="month"' . selected($cfg['period'], 'month', false) . '>' . esc_html__('月', 'yoone-subscriptions') . '</option>'
. '<option value="year"' . selected($cfg['period'], 'year', false) . '>' . esc_html__('年', 'yoone-subscriptions') . '</option>'
. '</select>'
. '</p>';
// 数量输入
echo '<p class="yoone-subs-row">'
. '<label>' . esc_html__('订阅数量', 'yoone-subscriptions') . '</label> '
. '<input type="number" name="yoone_sub_quantity" min="1" step="1" value="' . esc_attr($cfg['qty_default']) . '" />'
. '</p>';
// 价格与折扣展示(每周期价格)
echo '<p class="yoone-subs-price">'
. esc_html__('订阅价格(每周期):', 'yoone-subscriptions')
. wc_price($sub_price)
. ($discount > 0 ? ' <span class="yoone-subs-discount">' . sprintf(esc_html__('折扣约 %.1f%%', 'yoone-subscriptions'), $discount) . '</span>' : '')
. '</p>';
echo '</div>';
}
/**
* 校验加入购物车的订阅参数。
*/
public function validate_add_to_cart($passed, $product_id, $quantity, $variation_id, $variations, $cart_item_data) {
$product = wc_get_product($product_id);
if (! $product) return $passed;
$cfg = Yoone_Subscriptions::get_config($product);
if (! $cfg['enabled']) return $passed;
$mode = isset($_POST['yoone_sub_purchase_mode']) ? sanitize_text_field($_POST['yoone_sub_purchase_mode']) : '';
if ($cfg['allow_onetime'] && $mode === 'onetime') return $passed; // 一次性购买,无需校验订阅参数
$period = isset($_POST['yoone_sub_period']) ? sanitize_text_field($_POST['yoone_sub_period']) : $cfg['period'];
$qty = isset($_POST['yoone_sub_quantity']) ? absint($_POST['yoone_sub_quantity']) : $cfg['qty_default'];
$period = in_array($period, array('month','year'), true) ? $period : $cfg['period'];
$qty = max(1, $qty);
if ($mode !== 'subscribe' && ! $cfg['allow_onetime']) {
wc_add_notice(__('该产品仅支持订阅购买。', 'yoone-subscriptions'), 'error');
return false;
}
// 可根据需要扩展更多校验(库存、最大值等)
return $passed;
}
/**
* 将订阅参数写入购物车项。
*/
public function add_cart_item_data($cart_item_data, $product_id, $variation_id) {
$product = wc_get_product($product_id);
if (! $product) return $cart_item_data;
$cfg = Yoone_Subscriptions::get_config($product);
if (! $cfg['enabled']) return $cart_item_data;
$mode = isset($_POST['yoone_sub_purchase_mode']) ? sanitize_text_field($_POST['yoone_sub_purchase_mode']) : '';
$period = isset($_POST['yoone_sub_period']) ? sanitize_text_field($_POST['yoone_sub_period']) : $cfg['period'];
$qty = isset($_POST['yoone_sub_quantity']) ? absint($_POST['yoone_sub_quantity']) : $cfg['qty_default'];
$period = in_array($period, array('month','year'), true) ? $period : $cfg['period'];
$qty = max(1, $qty);
if ($cfg['allow_onetime'] && $mode === 'onetime') {
// 标记为一次性购买,便于显示
$cart_item_data['yoone_subscriptions'] = array(
'mode' => 'onetime',
);
return $cart_item_data;
}
$cart_item_data['yoone_subscriptions'] = array(
'mode' => 'subscribe',
'period' => $period,
'qty' => $qty,
'price' => ($cfg['price'] > 0 ? $cfg['price'] : floatval($product->get_price())), // 每周期价格
);
return $cart_item_data;
}
/**
* 购物车/订单行项目中展示订阅摘要。
*/
public function display_item_data($item_data, $cart_item) {
if (empty($cart_item['yoone_subscriptions'])) return $item_data;
$data = $cart_item['yoone_subscriptions'];
if ($data['mode'] === 'onetime') {
$item_data[] = array('key' => __('购买方式', 'yoone-subscriptions'), 'value' => __('一次性购买', 'yoone-subscriptions'));
return $item_data;
}
$period_label = $data['period'] === 'year' ? __('年', 'yoone-subscriptions') : __('月', 'yoone-subscriptions');
$item_data[] = array('key' => __('购买方式', 'yoone-subscriptions'), 'value' => __('订阅', 'yoone-subscriptions'));
$item_data[] = array('key' => __('周期', 'yoone-subscriptions'), 'value' => $period_label);
$item_data[] = array('key' => __('订阅数量', 'yoone-subscriptions'), 'value' => intval($data['qty']));
$item_data[] = array('key' => __('订阅价格(每周期)', 'yoone-subscriptions'), 'value' => wc_price(floatval($data['price'])));
return $item_data;
}
/**
* 根据订阅规则设置行项目价格:价格 = 每周期价格 × 周期系数 × 订阅数量 × 购物车数量。
*/
public function adjust_subscription_price($cart) {
if (is_admin() && ! defined('DOING_AJAX')) return;
if (empty($cart) || ! method_exists($cart, 'get_cart')) return;
foreach ($cart->get_cart() as $key => $item) {
if (empty($item['yoone_subscriptions'])) continue;
$data = $item['yoone_subscriptions'];
if ($data['mode'] !== 'subscribe') continue;
$product = isset($item['data']) ? $item['data'] : null;
if (! $product || ! is_a($product, 'WC_Product')) continue;
$price_per_cycle = floatval($data['price']);
$factor = Yoone_Subscriptions::period_factor($data['period']);
$sub_qty = absint($data['qty']);
$cart_qty = absint(isset($item['quantity']) ? $item['quantity'] : 1);
$line_price = $price_per_cycle * $factor * $sub_qty * $cart_qty;
$product->set_price($line_price);
}
}
}