feat(pay): points-to-VIP exchange and remaining-days upgrade pricing
Add pay_vip_points_exchange packages (level|points|days|title), REST redeem for non-VIP users, unit|daily upgrade product for residual VIP1 days, and UI.
This commit is contained in:
@@ -541,3 +541,22 @@
|
||||
color: #9ca3af;
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.uc-vip-shop--points {
|
||||
border-color: rgba(16, 185, 129, 0.25);
|
||||
}
|
||||
.uc-vip-shop__balance {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #047857;
|
||||
margin: 0 0 10px;
|
||||
}
|
||||
.uc-vip-shop__item.is-disabled {
|
||||
opacity: 0.55;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.uc-vip-shop__item.is-disabled:hover {
|
||||
transform: none;
|
||||
box-shadow: none;
|
||||
border-color: var(--color-line, #e5e7eb);
|
||||
}
|
||||
|
||||
+45
-1
@@ -318,7 +318,49 @@
|
||||
}
|
||||
|
||||
|
||||
// ===== VIP 开通 =====
|
||||
|
||||
// ===== VIP 积分兑换 =====
|
||||
function initVipPointsExchange() {
|
||||
var root = document.querySelector('[data-aksrt-vip-points]');
|
||||
if (!root) return;
|
||||
var tip = root.querySelector('[data-aksrt-vip-points-tip]');
|
||||
var busy = false;
|
||||
function setTip(text, isError) {
|
||||
if (!tip) return;
|
||||
tip.textContent = text || '';
|
||||
tip.classList.toggle('is-error', !!isError);
|
||||
}
|
||||
root.addEventListener('click', function (e) {
|
||||
var btn = e.target.closest('[data-aksrt-vip-points-buy]');
|
||||
if (!btn || busy || btn.disabled) return;
|
||||
e.preventDefault();
|
||||
var packageId = parseInt(btn.getAttribute('data-package-id') || '0', 10);
|
||||
if (!packageId) return;
|
||||
busy = true;
|
||||
btn.classList.add('is-loading');
|
||||
setTip('正在兑换…');
|
||||
api('/pay/vip/points-exchange', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ package_id: packageId })
|
||||
}).then(function (r) {
|
||||
busy = false;
|
||||
btn.classList.remove('is-loading');
|
||||
if (!r.ok || (r.json && r.json.code && !r.json.success)) {
|
||||
setTip((r.json && r.json.message) || '兑换失败', true);
|
||||
return;
|
||||
}
|
||||
var data = (r.json && r.json.data) ? r.json.data : r.json;
|
||||
setTip((data && data.message) || '兑换成功,正在刷新…');
|
||||
window.location.reload();
|
||||
}).catch(function () {
|
||||
busy = false;
|
||||
btn.classList.remove('is-loading');
|
||||
setTip('网络错误,请重试', true);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ===== VIP 开通 =====
|
||||
function initVipShop() {
|
||||
var root = document.querySelector('[data-aksrt-vip-shop]');
|
||||
if (!root) return;
|
||||
@@ -405,10 +447,12 @@
|
||||
initSignin();
|
||||
initInvite();
|
||||
initVipShop();
|
||||
initVipPointsExchange();
|
||||
});
|
||||
} else {
|
||||
initSignin();
|
||||
initInvite();
|
||||
initVipShop();
|
||||
initVipPointsExchange();
|
||||
}
|
||||
})();
|
||||
|
||||
@@ -107,7 +107,12 @@ class AKSRT_Admin_Schema_Payment {
|
||||
['key' => 'pay_user_vip_desc', 'label' => '会员说明', 'type' => 'textarea', 'default' => ''],
|
||||
]],
|
||||
['key' => 'packages', 'title' => '会员套餐', 'fields' => [
|
||||
['key' => 'payment_vip_packages', 'label' => '套餐列表', 'type' => 'textarea', 'default' => "30|30|月卡\n365|365|年卡"],
|
||||
['key' => 'payment_vip_packages', 'label' => '套餐列表', 'type' => 'textarea', 'default' => "30|30|月卡\n365|365|年卡", 'help' => '每行:现金价|天数|标题(天数为 0 表示永久)'],
|
||||
]],
|
||||
['key' => 'points_exchange', 'title' => '积分兑换会员', 'fields' => [
|
||||
['key' => 'pay_vip_points_exchange_s', 'label' => '启用积分兑换', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'payment_vip_points_packages', 'label' => '兑换套餐', 'type' => 'textarea', 'default' => "1|500|30|积分月卡\n1|5000|0|积分永久", 'dependsOn' => ['key' => 'pay_vip_points_exchange_s', 'value' => true], 'help' => '每行:等级|积分|天数|标题(天数 0=永久;仅未开通会员可兑)'],
|
||||
['key' => 'pay_vip_points_exchange_desc', 'label' => '兑换说明', 'type' => 'textarea', 'default' => '', 'dependsOn' => ['key' => 'pay_vip_points_exchange_s', 'value' => true]],
|
||||
]],
|
||||
['key' => 'benefit', 'title' => '下载权益', 'fields' => [
|
||||
['key' => 'pay_download_limit', 'label' => '普通用户下载限制', 'type' => 'number', 'default' => 0, 'min' => 0, 'max' => 9999],
|
||||
@@ -133,7 +138,7 @@ class AKSRT_Admin_Schema_Payment {
|
||||
['key' => 'upgrade', 'title' => '升级设置', 'fields' => [
|
||||
['key' => 'vip_upgrade', 'label' => '允许升级', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'vip_upgrade_desc', 'label' => '升级说明', 'type' => 'textarea', 'default' => '', 'dependsOn' => ['key' => 'vip_upgrade', 'value' => true]],
|
||||
['key' => 'vip_upgrade_product', 'label' => '升级产品', 'type' => 'input', 'default' => '', 'dependsOn' => ['key' => 'vip_upgrade', 'value' => true]],
|
||||
['key' => 'vip_upgrade_product', 'label' => '升级产品', 'type' => 'input', 'default' => '', 'dependsOn' => ['key' => 'vip_upgrade', 'value' => true], 'help' => '固定价:价格|天数|标题;按剩余天数:unit|每日单价|标题'],
|
||||
]],
|
||||
],
|
||||
],
|
||||
|
||||
@@ -41,6 +41,12 @@ class AKSRT_Vip_Service {
|
||||
'permission_callback' => [$this, 'check_rest_nonce'],
|
||||
],
|
||||
]);
|
||||
|
||||
register_rest_route('aksrt/v1', '/pay/vip/points-exchange', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'rest_points_exchange'],
|
||||
'permission_callback' => [$this, 'check_rest_nonce'],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -103,13 +109,34 @@ class AKSRT_Vip_Service {
|
||||
return new WP_REST_Response($result, 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /pay/vip/points-exchange
|
||||
* body: { package_id: int } // points package id = 5000 + index
|
||||
*/
|
||||
public function rest_points_exchange(WP_REST_Request $request) {
|
||||
$uid = get_current_user_id();
|
||||
if ($uid <= 0) {
|
||||
return new WP_Error('aksrt_need_login', __('请先登录后再兑换。', 'aksrt'), ['status' => 401]);
|
||||
}
|
||||
$params = $request->get_json_params();
|
||||
if (!is_array($params)) {
|
||||
$params = $request->get_params();
|
||||
}
|
||||
$package_id = (int) ($params['package_id'] ?? 0);
|
||||
$result = $this->exchange_with_points($uid, $package_id);
|
||||
if (is_wp_error($result)) {
|
||||
return $result;
|
||||
}
|
||||
return new WP_REST_Response($result, 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
public function get_catalog(int $user_id = 0): array {
|
||||
$vip = class_exists('AKSRT_Settings')
|
||||
? AKSRT_Settings::get_payment_vip_settings()
|
||||
: ['enabled' => false, 'packages_raw' => '', 'levels' => [], 'renew' => [], 'upgrade' => []];
|
||||
: ['enabled' => false, 'packages_raw' => '', 'levels' => [], 'renew' => [], 'upgrade' => [], 'points_exchange' => []];
|
||||
|
||||
$level = function_exists('aksrt_get_user_vip_level') ? aksrt_get_user_vip_level($user_id) : 0;
|
||||
$expires = $user_id > 0 ? (int) get_user_meta($user_id, 'aksrt_vip_expires', true) : 0;
|
||||
@@ -118,23 +145,23 @@ class AKSRT_Vip_Service {
|
||||
$levels = is_array($vip['levels'] ?? null) ? $vip['levels'] : [];
|
||||
$renew = is_array($vip['renew'] ?? null) ? $vip['renew'] : [];
|
||||
$upgrade = is_array($vip['upgrade'] ?? null) ? $vip['upgrade'] : [];
|
||||
$points_ex = is_array($vip['points_exchange'] ?? null) ? $vip['points_exchange'] : [];
|
||||
$renew_enabled = !isset($renew['enabled']) || !empty($renew['enabled']);
|
||||
$upgrade_enabled = !isset($upgrade['enabled']) || !empty($upgrade['enabled']);
|
||||
$points_enabled = !empty($points_ex['enabled'])
|
||||
&& (!class_exists('AKSRT_Settings') || AKSRT_Settings::credits_enabled());
|
||||
|
||||
$level_packages = [];
|
||||
foreach ([1, 2] as $lv) {
|
||||
if (empty($levels[$lv]['enabled'])) {
|
||||
continue;
|
||||
}
|
||||
// Permanent VIP at same/higher level: only show higher-level upgrade packages.
|
||||
if ($is_permanent && $lv <= $level) {
|
||||
continue;
|
||||
}
|
||||
// Existing VIP without renew: hide same-level packages.
|
||||
if ($level > 0 && $lv === $level && !$renew_enabled) {
|
||||
continue;
|
||||
}
|
||||
// Existing VIP without upgrade: hide higher-level packages.
|
||||
if ($level > 0 && $lv > $level && !$upgrade_enabled) {
|
||||
continue;
|
||||
}
|
||||
@@ -144,8 +171,6 @@ class AKSRT_Vip_Service {
|
||||
foreach ($packages as $idx => $pkg) {
|
||||
$base_price = (float) $pkg['price'];
|
||||
$days = (int) $pkg['days'];
|
||||
// Permanent members don't renew timed packages at same level (already filtered).
|
||||
// Non-permanent same-level: renew pricing.
|
||||
$action = 'pay';
|
||||
if ($level > 0 && $lv === $level) {
|
||||
$action = 'renew';
|
||||
@@ -159,57 +184,34 @@ class AKSRT_Vip_Service {
|
||||
}
|
||||
|
||||
$items[] = [
|
||||
'package_id' => $this->encode_package_id($lv, $idx + 1),
|
||||
'level' => $lv,
|
||||
'index' => $idx + 1,
|
||||
'action' => $action,
|
||||
'price' => (float) $priced['price'],
|
||||
'base_price' => $base_price,
|
||||
'show_price' => (float) ($priced['show_price'] ?? 0),
|
||||
'discount_tag' => (string) ($priced['tag'] ?? ''),
|
||||
'days' => $days,
|
||||
'title' => (string) $pkg['title'],
|
||||
'price_label' => $this->format_cash_label((float) $priced['price']),
|
||||
'package_id' => $this->encode_package_id($lv, $idx + 1),
|
||||
'level' => $lv,
|
||||
'index' => $idx + 1,
|
||||
'action' => $action,
|
||||
'price' => (float) $priced['price'],
|
||||
'base_price' => $base_price,
|
||||
'show_price' => (float) ($priced['show_price'] ?? 0),
|
||||
'discount_tag' => (string) ($priced['tag'] ?? ''),
|
||||
'days' => $days,
|
||||
'title' => (string) $pkg['title'],
|
||||
'price_label' => $this->format_cash_label((float) $priced['price']),
|
||||
'show_price_label' => !empty($priced['show_price'])
|
||||
? $this->format_cash_label((float) $priced['show_price'])
|
||||
: '',
|
||||
'days_label' => ($days <= 0)
|
||||
'days_label' => ($days <= 0)
|
||||
? __('永久', 'aksrt')
|
||||
: sprintf(
|
||||
/* translators: %d: days */
|
||||
__('%d 天', 'aksrt'),
|
||||
$days
|
||||
),
|
||||
'cta_label' => $action === 'renew'
|
||||
: sprintf(__('%d 天', 'aksrt'), $days),
|
||||
'cta_label' => $action === 'renew'
|
||||
? __('立即续费', 'aksrt')
|
||||
: ($action === 'upgrade' ? __('立即升级', 'aksrt') : __('立即开通', 'aksrt')),
|
||||
];
|
||||
}
|
||||
|
||||
// Optional dedicated upgrade permanent product line from settings.upgrade.product
|
||||
// Format: price|days|title (single package for VIP2 upgrade).
|
||||
// Dedicated upgrade product: fixed or remaining-days unit price.
|
||||
if ($level === 1 && $lv === 2 && $upgrade_enabled) {
|
||||
$upgrade_pkg = $this->parse_upgrade_product_line((string) ($upgrade['product'] ?? ''));
|
||||
if ($upgrade_pkg) {
|
||||
$items[] = [
|
||||
'package_id' => $this->encode_package_id(2, 901),
|
||||
'level' => 2,
|
||||
'index' => 901,
|
||||
'action' => 'upgrade',
|
||||
'price' => (float) $upgrade_pkg['price'],
|
||||
'base_price' => (float) $upgrade_pkg['price'],
|
||||
'show_price' => 0.0,
|
||||
'discount_tag' => __('升级专享', 'aksrt'),
|
||||
'days' => (int) $upgrade_pkg['days'],
|
||||
'title' => (string) $upgrade_pkg['title'],
|
||||
'price_label' => $this->format_cash_label((float) $upgrade_pkg['price']),
|
||||
'show_price_label' => '',
|
||||
'days_label' => ((int) $upgrade_pkg['days'] <= 0)
|
||||
? __('永久', 'aksrt')
|
||||
: sprintf(__('%d 天', 'aksrt'), (int) $upgrade_pkg['days']),
|
||||
'cta_label' => __('立即升级', 'aksrt'),
|
||||
'is_upgrade_product' => true,
|
||||
];
|
||||
$upgrade_items = $this->build_upgrade_items($user_id, $expires, $is_permanent, (string) ($upgrade['product'] ?? ''));
|
||||
foreach ($upgrade_items as $ui) {
|
||||
$items[] = $ui;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -224,7 +226,14 @@ class AKSRT_Vip_Service {
|
||||
];
|
||||
}
|
||||
|
||||
$points_packages = [];
|
||||
if ($points_enabled && $level <= 0) {
|
||||
$points_packages = $this->get_points_exchange_packages($vip, $levels);
|
||||
}
|
||||
|
||||
$balance = ($user_id > 0 && class_exists('AKSRT_Credits')) ? AKSRT_Credits::balance($user_id) : 0;
|
||||
$methods = $this->available_payment_methods();
|
||||
$currency_name = class_exists('AKSRT_Settings') ? AKSRT_Settings::get_currency_name() : __('积分', 'aksrt');
|
||||
|
||||
return [
|
||||
'enabled' => !empty($vip['enabled']),
|
||||
@@ -234,11 +243,18 @@ class AKSRT_Vip_Service {
|
||||
'user_expires' => $expires,
|
||||
'user_is_permanent' => $is_permanent,
|
||||
'user_expires_label' => $this->format_expires_label($expires, $level),
|
||||
'user_points' => $balance,
|
||||
'currency_name' => $currency_name,
|
||||
'renew_enabled' => $renew_enabled,
|
||||
'upgrade_enabled' => $upgrade_enabled,
|
||||
'renew_desc' => (string) ($renew['desc'] ?? ''),
|
||||
'upgrade_desc' => (string) ($upgrade['desc'] ?? ''),
|
||||
'level_packages' => $level_packages,
|
||||
'points_exchange' => [
|
||||
'enabled' => $points_enabled && $level <= 0,
|
||||
'desc' => (string) ($points_ex['desc'] ?? ''),
|
||||
'packages' => $points_packages,
|
||||
],
|
||||
'payment_methods' => $methods,
|
||||
'vip_url' => function_exists('aksrt_get_user_center_tab_url')
|
||||
? aksrt_get_user_center_tab_url('credits')
|
||||
@@ -301,17 +317,29 @@ class AKSRT_Vip_Service {
|
||||
$action = 'upgrade';
|
||||
}
|
||||
|
||||
// Dedicated upgrade product (index 901).
|
||||
if ($index === 901) {
|
||||
$upgrade_pkg = $this->parse_upgrade_product_line((string) ($upgrade['product'] ?? ''));
|
||||
if (!$upgrade_pkg || $level !== 2) {
|
||||
// Dedicated upgrade product (index 901 fixed, 902 unit remaining days).
|
||||
if ($index === 901 || $index === 902) {
|
||||
$upgrade_items = $this->build_upgrade_items($user_id, $expires, $is_permanent, (string) ($upgrade['product'] ?? ''));
|
||||
$match = null;
|
||||
foreach ($upgrade_items as $ui) {
|
||||
if ((int) ($ui['package_id'] ?? 0) === $package_id) {
|
||||
$match = $ui;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$match || $level !== 2) {
|
||||
return new WP_Error('aksrt_invalid_package', __('升级套餐不存在', 'aksrt'), ['status' => 400]);
|
||||
}
|
||||
$pkg = $upgrade_pkg;
|
||||
$amount = round((float) $pkg['price'], 2);
|
||||
$days = (int) $pkg['days'];
|
||||
$base_price = $amount;
|
||||
$discount_tag = __('升级专享', 'aksrt');
|
||||
$pkg = [
|
||||
'price' => (float) $match['price'],
|
||||
'days' => (int) $match['days'],
|
||||
'title' => (string) $match['title'],
|
||||
];
|
||||
$amount = round((float) $match['price'], 2);
|
||||
$days = (int) $match['days'];
|
||||
$base_price = (float) ($match['base_price'] ?? $amount);
|
||||
$discount_tag = (string) ($match['discount_tag'] ?? __('升级专享', 'aksrt'));
|
||||
$action = 'upgrade';
|
||||
} else {
|
||||
$packages = $this->parse_packages((string) ($vip['packages_raw'] ?? ''));
|
||||
$pkg = $packages[$index - 1] ?? null;
|
||||
@@ -561,6 +589,288 @@ class AKSRT_Vip_Service {
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Points exchange (non-VIP only, zibll pay_vip_points_exchange).
|
||||
*
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public function exchange_with_points(int $user_id, int $package_id) {
|
||||
if ($user_id <= 0) {
|
||||
return new WP_Error('aksrt_need_login', __('请先登录后再兑换。', 'aksrt'), ['status' => 401]);
|
||||
}
|
||||
if (!class_exists('AKSRT_Credits')) {
|
||||
return new WP_Error('aksrt_credits_unavailable', __('积分系统不可用', 'aksrt'), ['status' => 500]);
|
||||
}
|
||||
if (class_exists('AKSRT_Settings') && !AKSRT_Settings::credits_enabled()) {
|
||||
return new WP_Error('aksrt_credits_disabled', __('积分功能未启用', 'aksrt'), ['status' => 400]);
|
||||
}
|
||||
|
||||
$vip = class_exists('AKSRT_Settings')
|
||||
? AKSRT_Settings::get_payment_vip_settings()
|
||||
: [];
|
||||
$points_ex = is_array($vip['points_exchange'] ?? null) ? $vip['points_exchange'] : [];
|
||||
if (empty($points_ex['enabled'])) {
|
||||
return new WP_Error('aksrt_points_exchange_disabled', __('积分兑换会员未启用', 'aksrt'), ['status' => 400]);
|
||||
}
|
||||
|
||||
$user_level = function_exists('aksrt_get_user_vip_level') ? aksrt_get_user_vip_level($user_id) : 0;
|
||||
if ($user_level > 0) {
|
||||
return new WP_Error('aksrt_already_vip', __('您已是会员,无法使用积分兑换。', 'aksrt'), ['status' => 400]);
|
||||
}
|
||||
|
||||
$levels = is_array($vip['levels'] ?? null) ? $vip['levels'] : [];
|
||||
$packages = $this->get_points_exchange_packages($vip, $levels);
|
||||
$match = null;
|
||||
foreach ($packages as $pkg) {
|
||||
if ((int) ($pkg['package_id'] ?? 0) === $package_id) {
|
||||
$match = $pkg;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$match) {
|
||||
return new WP_Error('aksrt_invalid_package', __('兑换套餐不存在', 'aksrt'), ['status' => 400]);
|
||||
}
|
||||
|
||||
$cost = (int) ($match['points'] ?? 0);
|
||||
$level = (int) ($match['level'] ?? 1);
|
||||
$days = (int) ($match['days'] ?? 0);
|
||||
if ($cost <= 0) {
|
||||
return new WP_Error('aksrt_invalid_price', __('兑换积分无效', 'aksrt'), ['status' => 400]);
|
||||
}
|
||||
|
||||
$balance = AKSRT_Credits::balance($user_id);
|
||||
if ($balance < $cost) {
|
||||
return new WP_Error('aksrt_credits_insufficient', __('积分不足,暂时无法兑换', 'aksrt'), [
|
||||
'status' => 402,
|
||||
'balance' => $balance,
|
||||
'cost' => $cost,
|
||||
]);
|
||||
}
|
||||
|
||||
if (!class_exists('AKSRT_Order') || !class_exists('AKSRT_Order_Repository')) {
|
||||
return new WP_Error('aksrt_pay_unavailable', __('订单系统不可用', 'aksrt'), ['status' => 500]);
|
||||
}
|
||||
|
||||
$note = wp_json_encode([
|
||||
'vip_pay' => 1,
|
||||
'vip_pay_type' => 'points_exchange',
|
||||
'level' => $level,
|
||||
'days' => $days,
|
||||
'points' => $cost,
|
||||
'package_id' => $package_id,
|
||||
'package_title' => (string) ($match['title'] ?? ''),
|
||||
'action' => 'points_exchange',
|
||||
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
|
||||
$order_number = AKSRT_Order_Repository::get_instance()->create([
|
||||
'user_id' => $user_id,
|
||||
'product_type' => AKSRT_Domain_Model::ORDER_PRODUCT_VIP,
|
||||
'product_id' => $package_id,
|
||||
'amount' => (float) $cost,
|
||||
'currency' => 'points',
|
||||
'status' => AKSRT_Order::STATUS_PENDING,
|
||||
'payment_method' => 'points',
|
||||
'note' => is_string($note) ? $note : '',
|
||||
'expires_at' => gmdate('Y-m-d H:i:s', time() + 30 * MINUTE_IN_SECONDS),
|
||||
]);
|
||||
if ($order_number === '') {
|
||||
return new WP_Error('aksrt_order_create_failed', __('创建订单失败', 'aksrt'), ['status' => 500]);
|
||||
}
|
||||
|
||||
$deducted = AKSRT_Credits::deduct(
|
||||
$user_id,
|
||||
$cost,
|
||||
'vip_points_exchange',
|
||||
['order' => $order_number, 'level' => $level, 'days' => $days],
|
||||
sprintf(__('积分兑换会员 · %s', 'aksrt'), (string) ($match['title'] ?? ''))
|
||||
);
|
||||
if (is_wp_error($deducted) || $deducted === false) {
|
||||
AKSRT_Order::get_instance()->update_status($order_number, AKSRT_Order::STATUS_CANCELLED, [
|
||||
'note' => __('积分扣除失败,自动取消', 'aksrt'),
|
||||
]);
|
||||
$msg = is_wp_error($deducted) ? $deducted->get_error_message() : __('积分扣除失败', 'aksrt');
|
||||
return new WP_Error('aksrt_credits_deduct_failed', $msg ?: __('积分扣除失败', 'aksrt'), ['status' => 400]);
|
||||
}
|
||||
|
||||
$paid = AKSRT_Order::get_instance()->update_status($order_number, AKSRT_Order::STATUS_PAID, [
|
||||
'payment_method' => 'points',
|
||||
'payment_transaction_id' => 'points_' . $order_number,
|
||||
'note' => sprintf(__('积分兑换会员 %d 积分。', 'aksrt'), $cost),
|
||||
]);
|
||||
if (!$paid) {
|
||||
AKSRT_Credits::add(
|
||||
$user_id,
|
||||
$cost,
|
||||
'vip_points_exchange_refund',
|
||||
['order' => $order_number],
|
||||
__('会员兑换失败退款', 'aksrt')
|
||||
);
|
||||
return new WP_Error('aksrt_order_confirm_failed', __('兑换确认失败,已尝试退回积分', 'aksrt'), ['status' => 500]);
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'order_number' => $order_number,
|
||||
'points' => $cost,
|
||||
'level' => $level,
|
||||
'days' => $days,
|
||||
'balance' => AKSRT_Credits::balance($user_id),
|
||||
'message' => __('兑换成功', 'aksrt'),
|
||||
'reload' => true,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Build upgrade package items (fixed and/or remaining-days unit).
|
||||
*
|
||||
* upgrade.product formats:
|
||||
* - fixed: price|days|title
|
||||
* - unit: unit|daily_price|title (price = remaining_days * daily_price, days = remaining)
|
||||
*
|
||||
* @return array<int, array<string,mixed>>
|
||||
*/
|
||||
private function build_upgrade_items(int $user_id, int $expires, bool $is_permanent, string $raw): array {
|
||||
$raw = trim($raw);
|
||||
if ($raw === '') {
|
||||
return [];
|
||||
}
|
||||
$items = [];
|
||||
$lines = preg_split('/\r\n|\r|\n/', $raw) ?: [];
|
||||
$fixed_i = 0;
|
||||
foreach ($lines as $line) {
|
||||
$line = trim((string) $line);
|
||||
if ($line === '') {
|
||||
continue;
|
||||
}
|
||||
$parts = array_map('trim', explode('|', $line));
|
||||
$mode = strtolower((string) ($parts[0] ?? ''));
|
||||
|
||||
if ($mode === 'unit') {
|
||||
if ($is_permanent || $expires <= time()) {
|
||||
continue;
|
||||
}
|
||||
$daily = isset($parts[1]) ? (float) $parts[1] : 0.0;
|
||||
if ($daily <= 0) {
|
||||
continue;
|
||||
}
|
||||
$remaining = (int) ceil(max(1, ($expires - time()) / DAY_IN_SECONDS));
|
||||
$price = round(max(0.01, $remaining * $daily), 2);
|
||||
$title = sanitize_text_field((string) ($parts[2] ?? ''));
|
||||
if ($title === '') {
|
||||
$title = sprintf(__('按剩余 %d 天升级', 'aksrt'), $remaining);
|
||||
}
|
||||
$items[] = [
|
||||
'package_id' => $this->encode_package_id(2, 902),
|
||||
'level' => 2,
|
||||
'index' => 902,
|
||||
'action' => 'upgrade',
|
||||
'price' => $price,
|
||||
'base_price' => $price,
|
||||
'show_price' => 0.0,
|
||||
'discount_tag' => __('剩余天数', 'aksrt'),
|
||||
'days' => $remaining,
|
||||
'title' => $title,
|
||||
'price_label' => $this->format_cash_label($price),
|
||||
'show_price_label' => '',
|
||||
'days_label' => sprintf(__('%d 天', 'aksrt'), $remaining),
|
||||
'cta_label' => __('立即升级', 'aksrt'),
|
||||
'is_upgrade_product' => true,
|
||||
'upgrade_mode' => 'unit',
|
||||
];
|
||||
continue;
|
||||
}
|
||||
|
||||
$parsed = $this->parse_packages($line);
|
||||
if (empty($parsed[0])) {
|
||||
continue;
|
||||
}
|
||||
$fixed_i++;
|
||||
// Only one fixed upgrade product id 901.
|
||||
if ($fixed_i > 1) {
|
||||
continue;
|
||||
}
|
||||
$pkg = $parsed[0];
|
||||
$items[] = [
|
||||
'package_id' => $this->encode_package_id(2, 901),
|
||||
'level' => 2,
|
||||
'index' => 901,
|
||||
'action' => 'upgrade',
|
||||
'price' => (float) $pkg['price'],
|
||||
'base_price' => (float) $pkg['price'],
|
||||
'show_price' => 0.0,
|
||||
'discount_tag' => __('升级专享', 'aksrt'),
|
||||
'days' => (int) $pkg['days'],
|
||||
'title' => (string) $pkg['title'],
|
||||
'price_label' => $this->format_cash_label((float) $pkg['price']),
|
||||
'show_price_label' => '',
|
||||
'days_label' => ((int) $pkg['days'] <= 0)
|
||||
? __('永久', 'aksrt')
|
||||
: sprintf(__('%d 天', 'aksrt'), (int) $pkg['days']),
|
||||
'cta_label' => __('立即升级', 'aksrt'),
|
||||
'is_upgrade_product' => true,
|
||||
'upgrade_mode' => 'fixed',
|
||||
];
|
||||
}
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Points exchange packages: level|points|days|title
|
||||
*
|
||||
* @param array<string,mixed> $vip
|
||||
* @param array<int,mixed> $levels
|
||||
* @return array<int, array<string,mixed>>
|
||||
*/
|
||||
public function get_points_exchange_packages(array $vip, array $levels): array {
|
||||
$points_ex = is_array($vip['points_exchange'] ?? null) ? $vip['points_exchange'] : [];
|
||||
$raw = (string) ($points_ex['packages_raw'] ?? '');
|
||||
$currency = class_exists('AKSRT_Settings') ? AKSRT_Settings::get_currency_name() : __('积分', 'aksrt');
|
||||
$out = [];
|
||||
$lines = preg_split('/\r\n|\r|\n/', $raw) ?: [];
|
||||
$i = 0;
|
||||
foreach ($lines as $line) {
|
||||
$line = trim((string) $line);
|
||||
if ($line === '') {
|
||||
continue;
|
||||
}
|
||||
$parts = array_map('trim', explode('|', $line));
|
||||
if (count($parts) < 3) {
|
||||
continue;
|
||||
}
|
||||
$lv = (int) ($parts[0] ?? 0);
|
||||
$pts = (int) ($parts[1] ?? 0);
|
||||
$days = (int) ($parts[2] ?? 0);
|
||||
$title = sanitize_text_field((string) ($parts[3] ?? ''));
|
||||
if ($lv < 1 || $lv > 2 || $pts <= 0) {
|
||||
continue;
|
||||
}
|
||||
if (empty($levels[$lv]['enabled'])) {
|
||||
continue;
|
||||
}
|
||||
$i++;
|
||||
if ($title === '') {
|
||||
$lv_name = (string) ($levels[$lv]['name'] ?? ('VIP' . $lv));
|
||||
$title = $days > 0
|
||||
? sprintf(__('%1$s · %2$d天', 'aksrt'), $lv_name, $days)
|
||||
: sprintf(__('%s · 永久', 'aksrt'), $lv_name);
|
||||
}
|
||||
$out[] = [
|
||||
'package_id' => 5000 + $i,
|
||||
'level' => $lv,
|
||||
'index' => $i,
|
||||
'points' => $pts,
|
||||
'days' => max(0, $days),
|
||||
'title' => $title,
|
||||
'price_label' => $pts . ' ' . $currency,
|
||||
'days_label' => $days > 0 ? sprintf(__('%d 天', 'aksrt'), $days) : __('永久', 'aksrt'),
|
||||
'cta_label' => __('立即兑换', 'aksrt'),
|
||||
];
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Single upgrade product line: price|days|title
|
||||
*
|
||||
@@ -571,10 +881,13 @@ class AKSRT_Vip_Service {
|
||||
if ($raw === '') {
|
||||
return null;
|
||||
}
|
||||
// Allow multiline; take first valid.
|
||||
$lines = preg_split('/\r\n|\r|\n/', $raw) ?: [];
|
||||
foreach ($lines as $line) {
|
||||
$parsed = $this->parse_packages((string) $line);
|
||||
$line = trim((string) $line);
|
||||
if ($line === '' || str_starts_with(strtolower($line), 'unit|')) {
|
||||
continue;
|
||||
}
|
||||
$parsed = $this->parse_packages($line);
|
||||
if (!empty($parsed[0])) {
|
||||
return $parsed[0];
|
||||
}
|
||||
@@ -604,7 +917,6 @@ class AKSRT_Vip_Service {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Permanent same-level packages never make sense for renew.
|
||||
if ($action === 'renew' && $is_permanent) {
|
||||
return null;
|
||||
}
|
||||
@@ -623,7 +935,6 @@ class AKSRT_Vip_Service {
|
||||
$tag = '';
|
||||
|
||||
if ($type === 'discount') {
|
||||
// Settings store "8" meaning 8折 (80%) when <= 10; or percent when > 10.
|
||||
$discount = (float) ($renew['discount'] ?? 8);
|
||||
if ($discount <= 0) {
|
||||
$discount = 8;
|
||||
@@ -648,7 +959,6 @@ class AKSRT_Vip_Service {
|
||||
$this->format_cash_label($reduce)
|
||||
);
|
||||
} else {
|
||||
// same
|
||||
$show = 0.0;
|
||||
$tag = __('续费同价', 'aksrt');
|
||||
}
|
||||
|
||||
@@ -193,6 +193,11 @@ class AKSRT_Settings_Payment_Runtime {
|
||||
'desc' => sanitize_textarea_field((string) ($settings['vip_upgrade_desc'] ?? '')),
|
||||
'product' => sanitize_text_field((string) ($settings['vip_upgrade_product'] ?? '')),
|
||||
],
|
||||
'points_exchange' => [
|
||||
'enabled' => !empty($settings['pay_vip_points_exchange_s']),
|
||||
'desc' => sanitize_textarea_field((string) ($settings['pay_vip_points_exchange_desc'] ?? '')),
|
||||
'packages_raw' => (string) ($settings['payment_vip_points_packages'] ?? "1|500|30|积分月卡\n1|5000|0|积分永久"),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
/**
|
||||
* User center credits: VIP membership card + purchase packages.
|
||||
* User center credits: VIP membership card + purchase packages + points exchange.
|
||||
*
|
||||
* Expects extracted $context vars.
|
||||
*
|
||||
@@ -41,12 +41,20 @@ if ($vip_level >= 1 && !empty($vip_settings['levels'][$vip_level]['name'])) {
|
||||
$packages = [];
|
||||
$renew_desc = '';
|
||||
$upgrade_desc = '';
|
||||
$points_exchange = ['enabled' => false, 'packages' => [], 'desc' => ''];
|
||||
$user_points = 0;
|
||||
$currency_name = class_exists('AKSRT_Settings') ? AKSRT_Settings::get_currency_name() : __('积分', 'aksrt');
|
||||
if (class_exists('AKSRT_Vip_Service')) {
|
||||
$catalog = AKSRT_Vip_Service::get_instance()->get_catalog($user_id);
|
||||
$packages = is_array($catalog['level_packages'] ?? null) ? $catalog['level_packages'] : [];
|
||||
$methods = is_array($catalog['payment_methods'] ?? null) ? $catalog['payment_methods'] : [];
|
||||
$renew_desc = (string) ($catalog['renew_desc'] ?? '');
|
||||
$upgrade_desc = (string) ($catalog['upgrade_desc'] ?? '');
|
||||
$points_exchange = is_array($catalog['points_exchange'] ?? null) ? $catalog['points_exchange'] : $points_exchange;
|
||||
$user_points = (int) ($catalog['user_points'] ?? 0);
|
||||
if (!empty($catalog['currency_name'])) {
|
||||
$currency_name = (string) $catalog['currency_name'];
|
||||
}
|
||||
} else {
|
||||
$methods = [['id' => 'balance', 'name' => __('站内余额', 'aksrt')]];
|
||||
}
|
||||
@@ -61,6 +69,7 @@ $shop_title = __('开通会员', 'aksrt');
|
||||
if ($vip_level > 0) {
|
||||
$shop_title = __('续费 / 升级会员', 'aksrt');
|
||||
}
|
||||
$points_pkgs = is_array($points_exchange['packages'] ?? null) ? $points_exchange['packages'] : [];
|
||||
?>
|
||||
<div class="uc-vip-card<?php echo $is_vip_user ? ' uc-vip-card--active' : ''; ?>" data-aksrt-vip-panel>
|
||||
<div class="uc-vip-card__bg"></div>
|
||||
@@ -183,6 +192,52 @@ if ($vip_level > 0) {
|
||||
|
||||
<p class="uc-vip-shop__tip" data-aksrt-vip-tip></p>
|
||||
</div>
|
||||
<?php elseif (!empty($vip_settings['help_text'])) : ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($points_exchange['enabled']) && $points_pkgs !== []) : ?>
|
||||
<div class="uc-vip-shop uc-vip-shop--points" data-aksrt-vip-points>
|
||||
<div class="uc-vip-shop__title">
|
||||
<i class="fas fa-coins" aria-hidden="true"></i>
|
||||
<span><?php esc_html_e('积分兑换会员', 'aksrt'); ?></span>
|
||||
</div>
|
||||
<div class="uc-vip-shop__balance">
|
||||
<?php
|
||||
printf(
|
||||
/* translators: 1: points balance 2: currency name */
|
||||
esc_html__('我的积分:%1$s %2$s', 'aksrt'),
|
||||
esc_html((string) $user_points),
|
||||
esc_html($currency_name)
|
||||
);
|
||||
?>
|
||||
</div>
|
||||
<?php if (!empty($points_exchange['desc'])) : ?>
|
||||
<p class="uc-vip-shop__note"><?php echo esc_html((string) $points_exchange['desc']); ?></p>
|
||||
<?php endif; ?>
|
||||
<div class="uc-vip-shop__grid">
|
||||
<?php foreach ($points_pkgs as $item) :
|
||||
$pid = (int) ($item['package_id'] ?? 0);
|
||||
$cost = (int) ($item['points'] ?? 0);
|
||||
$enough = $user_points >= $cost;
|
||||
?>
|
||||
<button
|
||||
type="button"
|
||||
class="uc-vip-shop__item<?php echo $enough ? '' : ' is-disabled'; ?>"
|
||||
data-aksrt-vip-points-buy
|
||||
data-package-id="<?php echo esc_attr((string) $pid); ?>"
|
||||
data-points="<?php echo esc_attr((string) $cost); ?>"
|
||||
<?php disabled(!$enough); ?>
|
||||
>
|
||||
<span class="uc-vip-shop__item-title"><?php echo esc_html((string) ($item['title'] ?? '')); ?></span>
|
||||
<span class="uc-vip-shop__item-days"><?php echo esc_html((string) ($item['days_label'] ?? '')); ?></span>
|
||||
<span class="uc-vip-shop__item-price"><?php echo esc_html((string) ($item['price_label'] ?? '')); ?></span>
|
||||
<span class="uc-vip-shop__item-cta">
|
||||
<?php echo $enough ? esc_html__('立即兑换', 'aksrt') : esc_html__('积分不足', 'aksrt'); ?>
|
||||
</span>
|
||||
</button>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<p class="uc-vip-shop__tip" data-aksrt-vip-points-tip></p>
|
||||
</div>
|
||||
<?php elseif (empty($packages) && !empty($vip_settings['help_text'])) : ?>
|
||||
<p class="uc-vip-shop__empty"><?php echo esc_html((string) $vip_settings['help_text']); ?></p>
|
||||
<?php endif; ?>
|
||||
|
||||
Reference in New Issue
Block a user