172 lines
5.2 KiB
PHP
172 lines
5.2 KiB
PHP
<?php
|
|
/**
|
|
* AKSRT Discount Engine
|
|
*
|
|
* 优惠活动计算引擎 — reduction 立减 / discount 打折 / gift 买赠。
|
|
* scope: item / product / author / order。
|
|
* 绑定在 shop_discount taxonomy 上,商品可挂多个活动。
|
|
*
|
|
* @package AKSRT
|
|
*/
|
|
|
|
defined('ABSPATH') || exit;
|
|
|
|
class AKSRT_Discount {
|
|
|
|
private static $instance = null;
|
|
|
|
public static function get_instance(): self {
|
|
if (null === self::$instance) {
|
|
self::$instance = new self();
|
|
}
|
|
return self::$instance;
|
|
}
|
|
|
|
/**
|
|
* Get all active discount campaigns for a product.
|
|
*
|
|
* @param int $product_id
|
|
* @return array<int, array> Sorted by priority desc.
|
|
*/
|
|
public function get_active_discounts(int $product_id): array {
|
|
$terms = wp_get_object_terms($product_id, 'shop_discount', ['fields' => 'all']);
|
|
if (is_wp_error($terms) || empty($terms)) {
|
|
return [];
|
|
}
|
|
|
|
$now = current_time('timestamp');
|
|
$active = [];
|
|
|
|
foreach ($terms as $term) {
|
|
$meta = AKSRT_Shop_Term_Meta::get_discount_meta($term->term_id);
|
|
|
|
// Time limit check
|
|
if (!empty($meta['start']) && strtotime($meta['start']) > $now) {
|
|
continue;
|
|
}
|
|
if (!empty($meta['end']) && strtotime($meta['end']) < $now) {
|
|
continue;
|
|
}
|
|
|
|
$meta['term_id'] = $term->term_id;
|
|
$meta['name'] = $term->name;
|
|
$active[] = $meta;
|
|
}
|
|
|
|
// Sort by priority desc
|
|
usort($active, function ($a, $b) {
|
|
return ((int) $b['priority']) - ((int) $a['priority']);
|
|
});
|
|
|
|
return $active;
|
|
}
|
|
|
|
/**
|
|
* Calculate discount for a single line item.
|
|
*
|
|
* @param float $unit_price Unit price
|
|
* @param int $quantity Quantity
|
|
* @param int $product_id Product ID
|
|
* @param int $user_id User ID (for user_limit checks)
|
|
* @return array{
|
|
* original: float,
|
|
* discount_amount: float,
|
|
* final_price: float,
|
|
* gifts: array,
|
|
* applied: array<int, array>
|
|
* }
|
|
*/
|
|
public function calculate(float $unit_price, int $quantity, int $product_id, int $user_id = 0): array {
|
|
$original = $unit_price * $quantity;
|
|
$discount_amount = 0.0;
|
|
$gifts = [];
|
|
$applied = [];
|
|
|
|
$discounts = $this->get_active_discounts($product_id);
|
|
|
|
foreach ($discounts as $d) {
|
|
// User limit check
|
|
if (!empty($d['user_limit']) && $user_id > 0) {
|
|
if (!$this->check_user_limit($d['user_limit'], $user_id)) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Price limit check
|
|
if ((float) $d['price_limit'] > 0 && $original < (float) $d['price_limit']) {
|
|
continue;
|
|
}
|
|
|
|
$type = $d['type'] ?? 'reduction';
|
|
$value = (float) ($d['value'] ?? 0);
|
|
|
|
switch ($type) {
|
|
case 'reduction':
|
|
// 立减
|
|
$discount_amount += min($value * $quantity, $original - $discount_amount);
|
|
break;
|
|
|
|
case 'discount':
|
|
// 打折 (value = 折扣系数 e.g. 8.5 = 85折)
|
|
if ($value > 0 && $value < 10) {
|
|
$rate = $value / 10.0;
|
|
$save = ($original - $discount_amount) * (1 - $rate);
|
|
$discount_amount += $save;
|
|
}
|
|
break;
|
|
|
|
case 'gift':
|
|
// 买赠 — collect gift config, no price reduction
|
|
if (!empty($d['gift_config'])) {
|
|
$gift_data = json_decode($d['gift_config'], true);
|
|
if (is_array($gift_data)) {
|
|
$gifts[] = $gift_data;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
$applied[] = [
|
|
'term_id' => $d['term_id'],
|
|
'name' => $d['name'] ?? '',
|
|
'type' => $type,
|
|
'value' => $value,
|
|
];
|
|
}
|
|
|
|
$discount_amount = min($discount_amount, $original);
|
|
|
|
return [
|
|
'original' => $original,
|
|
'discount_amount' => round($discount_amount, 2),
|
|
'final_price' => round($original - $discount_amount, 2),
|
|
'gifts' => $gifts,
|
|
'applied' => $applied,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Check if a user meets the user_limit requirement.
|
|
*
|
|
* @param string $limit 'vip' / 'vip_2' / 'auth'
|
|
* @param int $user_id
|
|
* @return bool
|
|
*/
|
|
private function check_user_limit(string $limit, int $user_id): bool {
|
|
if (empty($limit)) {
|
|
return true;
|
|
}
|
|
|
|
switch ($limit) {
|
|
case 'vip':
|
|
return function_exists('aksrt_is_vip') ? aksrt_is_vip($user_id) : false;
|
|
case 'vip_2':
|
|
return function_exists('aksrt_is_vip2') ? aksrt_is_vip2($user_id) : false;
|
|
case 'auth':
|
|
return function_exists('aksrt_is_verified') ? aksrt_is_verified($user_id) : false;
|
|
default:
|
|
return true;
|
|
}
|
|
}
|
|
}
|