feat(forum): P1 topic coupons, P2 plate follow gateway, cash creator revenue
- Topic_Pay: soft/hard coupon validate, payable amount, free-after-discount, paid burn - Creator_Revenue: settle content/topic/plate_follow cash (type=order), keep shop path - Plate VIP1/VIP2 meta + resolve_plate_follow_pay_price via aksrt_get_user_vip_level - New AKSRT_Plate_Follow_Pay REST multi-gateway; remove Credits×100 money bypass - Frontend: cms-download plate_follow apiKind + forum-governance money cashier bridge
This commit is contained in:
1 parent
1a15ae7cea
commit
78d3189f8f
14 files changed
+1189
-169
No files matched your search
@@ -408,7 +408,7 @@ trait AKSRT_Forum_Board_Service_Part_2 {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{amount:float|int,modo:string,is_vip_price:bool,is_free:bool,label:string,base:float|int,currency:string}
|
||||
* @return array{amount:float|int,modo:string,is_vip_price:bool,is_free:bool,label:string,base:float|int,currency:string,vip_level:int,vip_set:bool}
|
||||
*/
|
||||
public function resolve_plate_follow_pay_price(int $plate_id, int $user_id = 0): array {
|
||||
$modo = sanitize_key((string) (get_post_meta($plate_id, '_aksrt_follow_pay_modo', true) ?: 'money'));
|
||||
@@ -421,19 +421,54 @@ trait AKSRT_Forum_Board_Service_Part_2 {
|
||||
? max(0, (int) get_post_meta($plate_id, '_aksrt_follow_pay_points', true))
|
||||
: max(0.0, (float) get_post_meta($plate_id, '_aksrt_follow_pay_price', true));
|
||||
|
||||
$vip_raw = get_post_meta($plate_id, '_aksrt_follow_pay_vip_price', true);
|
||||
$vip_set = ($vip_raw !== '' && $vip_raw !== null && $vip_raw !== false);
|
||||
$vip_price = $vip_set
|
||||
? ($is_points ? max(0, (int) $vip_raw) : max(0.0, (float) $vip_raw))
|
||||
: 0;
|
||||
|
||||
$is_vip = $user_id > 0 && function_exists('aksrt_is_vip') && aksrt_is_vip($user_id);
|
||||
if (!$is_vip && $user_id > 0 && function_exists('aksrt_is_vip_exempt') && aksrt_is_vip_exempt($user_id)) {
|
||||
$is_vip = true;
|
||||
$vip_level = 0;
|
||||
if ($user_id > 0) {
|
||||
if (function_exists('aksrt_get_user_vip_level')) {
|
||||
$vip_level = (int) aksrt_get_user_vip_level($user_id);
|
||||
} elseif (function_exists('aksrt_is_vip2') && aksrt_is_vip2($user_id)) {
|
||||
$vip_level = 2;
|
||||
} elseif (function_exists('aksrt_is_vip') && aksrt_is_vip($user_id)) {
|
||||
$vip_level = 1;
|
||||
}
|
||||
if ($vip_level <= 0 && function_exists('aksrt_is_vip_exempt') && aksrt_is_vip_exempt($user_id)) {
|
||||
$vip_level = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if ($is_vip && $vip_set) {
|
||||
$amount = $vip_price;
|
||||
$pick = static function ($raw) use ($is_points) {
|
||||
if ($raw === '' || $raw === null || $raw === false) {
|
||||
return null;
|
||||
}
|
||||
if (!is_numeric($raw)) {
|
||||
return null;
|
||||
}
|
||||
return $is_points ? max(0, (int) $raw) : max(0.0, (float) $raw);
|
||||
};
|
||||
|
||||
$vip2 = $pick(get_post_meta($plate_id, '_aksrt_follow_pay_vip2_price', true));
|
||||
$vip1 = $pick(get_post_meta($plate_id, '_aksrt_follow_pay_vip1_price', true));
|
||||
if ($vip1 === null) {
|
||||
// Legacy single VIP key → vip1.
|
||||
$vip1 = $pick(get_post_meta($plate_id, '_aksrt_follow_pay_vip_price', true));
|
||||
}
|
||||
|
||||
$amount = $base;
|
||||
$is_vip_price = false;
|
||||
$vip_set = ($vip1 !== null || $vip2 !== null);
|
||||
|
||||
if ($vip_level >= 2 && $vip2 !== null) {
|
||||
$amount = $vip2;
|
||||
$is_vip_price = true;
|
||||
} elseif ($vip_level >= 1 && $vip1 !== null) {
|
||||
$amount = $vip1;
|
||||
$is_vip_price = true;
|
||||
} elseif ($vip_level >= 2 && $vip1 !== null) {
|
||||
// VIP2 user falls back to VIP1 when VIP2 unset.
|
||||
$amount = $vip1;
|
||||
$is_vip_price = true;
|
||||
}
|
||||
|
||||
if ($is_vip_price) {
|
||||
return [
|
||||
'amount' => $amount,
|
||||
'modo' => $modo,
|
||||
@@ -446,6 +481,8 @@ trait AKSRT_Forum_Board_Service_Part_2 {
|
||||
: sprintf(__('VIP ¥%s', 'aksrt'), number_format((float) $amount, 2, '.', ''))),
|
||||
'base' => $base,
|
||||
'currency' => $is_points ? 'points' : 'CNY',
|
||||
'vip_level' => $vip_level,
|
||||
'vip_set' => true,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -459,6 +496,8 @@ trait AKSRT_Forum_Board_Service_Part_2 {
|
||||
: sprintf(__('¥%s', 'aksrt'), number_format((float) $base, 2, '.', '')),
|
||||
'base' => $base,
|
||||
'currency' => $is_points ? 'points' : 'CNY',
|
||||
'vip_level' => $vip_level,
|
||||
'vip_set' => $vip_set,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -549,69 +588,12 @@ trait AKSRT_Forum_Board_Service_Part_2 {
|
||||
]);
|
||||
}
|
||||
|
||||
// Cash via balance credits (same as topic pay).
|
||||
if (!class_exists('AKSRT_Order') || !class_exists('AKSRT_Domain_Model')) {
|
||||
return $this->forum->error(__('支付系统不可用', 'aksrt'));
|
||||
}
|
||||
$cash = max(0.0, (float) $amount);
|
||||
if ($cash <= 0) {
|
||||
return $this->forum->error(__('价格未设置', 'aksrt'));
|
||||
}
|
||||
$product_type = AKSRT_Domain_Model::ORDER_PRODUCT_PLATE_FOLLOW;
|
||||
$order_number = AKSRT_Order::get_instance()->create($user_id, $product_type, $plate_id, $cash, 'CNY');
|
||||
if ($order_number === '') {
|
||||
return $this->forum->error(__('创建订单失败', 'aksrt'));
|
||||
}
|
||||
$credits_cost = (int) ceil($cash * 100);
|
||||
$balance = AKSRT_Credits::get_balance($user_id);
|
||||
if ($balance < $credits_cost) {
|
||||
AKSRT_Order::get_instance()->update_status($order_number, AKSRT_Order::STATUS_CANCELLED, [
|
||||
'note' => __('余额不足,自动取消', 'aksrt'),
|
||||
]);
|
||||
return $this->forum->error(sprintf(
|
||||
__('余额不足,需要 %d 积分(约 ¥%s),当前余额 %d', 'aksrt'),
|
||||
$credits_cost,
|
||||
number_format($cash, 2, '.', ''),
|
||||
$balance
|
||||
));
|
||||
}
|
||||
$deducted = AKSRT_Credits::deduct(
|
||||
$user_id,
|
||||
$credits_cost,
|
||||
'forum_plate_follow_pay',
|
||||
['plate_id' => $plate_id, 'order' => $order_number],
|
||||
sprintf(__('付费关注版块「%s」', 'aksrt'), get_the_title($plate_id))
|
||||
);
|
||||
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 $this->forum->error($msg ?: __('扣款失败', 'aksrt'));
|
||||
}
|
||||
$paid = AKSRT_Order::get_instance()->update_status($order_number, AKSRT_Order::STATUS_PAID, [
|
||||
'payment_method' => 'balance',
|
||||
'payment_transaction_id' => 'BAL_' . $order_number,
|
||||
'note' => sprintf(__('版块付费关注余额支付 %d 积分', 'aksrt'), $credits_cost),
|
||||
]);
|
||||
if (!$paid) {
|
||||
AKSRT_Credits::add(
|
||||
$user_id,
|
||||
$credits_cost,
|
||||
'forum_plate_follow_refund',
|
||||
['plate_id' => $plate_id, 'order' => $order_number],
|
||||
sprintf(__('关注失败退款:%s', 'aksrt'), get_the_title($plate_id))
|
||||
);
|
||||
return $this->forum->error(__('订单确认失败,已尝试退回积分', 'aksrt'));
|
||||
}
|
||||
|
||||
$this->mark_plate_followed($user_id, $plate_id);
|
||||
return $this->forum->success(__('关注成功', 'aksrt'), [
|
||||
'followed' => true,
|
||||
'follow_count' => (int) get_post_meta($plate_id, '_aksrt_follow_count', true),
|
||||
'order_number' => $order_number,
|
||||
'amount' => $cash,
|
||||
'credits_cost' => $credits_cost,
|
||||
// Cash: multi-gateway cashier only (AKSRT_Plate_Follow_Pay REST). No Credits×100 bypass.
|
||||
return $this->forum->error(__('请通过收银台支付关注费用。', 'aksrt'), [
|
||||
'require_cashier' => true,
|
||||
'pay' => $price,
|
||||
'modo' => 'money',
|
||||
'plate_id' => $plate_id,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -165,9 +165,16 @@ trait AKSRT_Forum_Board_Service_Part_3 {
|
||||
|
||||
$result = $this->purchase_plate_follow($user_id, $plate_id);
|
||||
if (empty($result['success'])) {
|
||||
wp_send_json_error([
|
||||
$err = [
|
||||
'message' => (string) ($result['message'] ?? __('支付失败', 'aksrt')),
|
||||
]);
|
||||
];
|
||||
if (!empty($result['require_cashier'])) {
|
||||
$err['require_cashier'] = true;
|
||||
$err['pay'] = $result['pay'] ?? [];
|
||||
$err['modo'] = (string) ($result['modo'] ?? 'money');
|
||||
$err['plate_id'] = (int) ($result['plate_id'] ?? $plate_id);
|
||||
}
|
||||
wp_send_json_error($err);
|
||||
}
|
||||
wp_send_json_success($result);
|
||||
}
|
||||
|
||||
@@ -168,9 +168,10 @@ class AKSRT_Plate_Meta_Box {
|
||||
<p>
|
||||
<label for="aksrt_plate_follow_pay_modo"><strong><?php esc_html_e('关注支付方式', 'aksrt'); ?></strong></label>
|
||||
<select id="aksrt_plate_follow_pay_modo" name="aksrt_plate_follow_pay_modo" class="widefat">
|
||||
<option value="money" <?php selected(($follow_pay['modo'] ?? 'money'), 'money'); ?>><?php esc_html_e('现金(余额)', 'aksrt'); ?></option>
|
||||
<option value="money" <?php selected(($follow_pay['modo'] ?? 'money'), 'money'); ?>><?php esc_html_e('现金', 'aksrt'); ?></option>
|
||||
<option value="points" <?php selected(($follow_pay['modo'] ?? ''), 'points'); ?>><?php esc_html_e('积分', 'aksrt'); ?></option>
|
||||
</select>
|
||||
<span class="description" style="display:block;"><?php esc_html_e('现金走多网关收银台;积分仍即时扣点。', 'aksrt'); ?></span>
|
||||
</p>
|
||||
<p>
|
||||
<label for="aksrt_plate_follow_pay_price"><strong><?php esc_html_e('现金价格(元)', 'aksrt'); ?></strong></label>
|
||||
@@ -181,9 +182,13 @@ class AKSRT_Plate_Meta_Box {
|
||||
<input type="number" step="1" min="0" class="widefat" name="aksrt_plate_follow_pay_points" id="aksrt_plate_follow_pay_points" value="<?php echo esc_attr((string) (int) ($follow_pay['points'] ?? 0)); ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label for="aksrt_plate_follow_pay_vip_price"><strong><?php esc_html_e('VIP 价格(可选)', 'aksrt'); ?></strong></label>
|
||||
<input type="number" step="0.01" min="0" class="widefat" name="aksrt_plate_follow_pay_vip_price" id="aksrt_plate_follow_pay_vip_price" value="<?php echo esc_attr((string) ($follow_pay['vip_price'] ?? '')); ?>" placeholder="<?php esc_attr_e('留空=不设 VIP 价;0=VIP 免费', 'aksrt'); ?>">
|
||||
<span class="description"><?php esc_html_e('现金模式用元;积分模式填积分数。', 'aksrt'); ?></span>
|
||||
<label for="aksrt_plate_follow_pay_vip1_price"><strong><?php esc_html_e('VIP1 价格(可选)', 'aksrt'); ?></strong></label>
|
||||
<input type="number" step="0.01" min="0" class="widefat" name="aksrt_plate_follow_pay_vip1_price" id="aksrt_plate_follow_pay_vip1_price" value="<?php echo esc_attr((string) ($follow_pay['vip1_price'] ?? '')); ?>" placeholder="<?php esc_attr_e('留空=不设 VIP1 价;0=VIP1 免费', 'aksrt'); ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label for="aksrt_plate_follow_pay_vip2_price"><strong><?php esc_html_e('VIP2 价格(可选)', 'aksrt'); ?></strong></label>
|
||||
<input type="number" step="0.01" min="0" class="widefat" name="aksrt_plate_follow_pay_vip2_price" id="aksrt_plate_follow_pay_vip2_price" value="<?php echo esc_attr((string) ($follow_pay['vip2_price'] ?? '')); ?>" placeholder="<?php esc_attr_e('留空=不设 VIP2 价;0=VIP2 免费', 'aksrt'); ?>">
|
||||
<span class="description"><?php esc_html_e('现金模式用元;积分模式填积分数。未填 VIP1 时仍可读旧「VIP 价格」字段。', 'aksrt'); ?></span>
|
||||
</p>
|
||||
<hr>
|
||||
<?php
|
||||
@@ -391,6 +396,8 @@ class AKSRT_Plate_Meta_Box {
|
||||
delete_post_meta($post_id, '_aksrt_follow_pay_price');
|
||||
delete_post_meta($post_id, '_aksrt_follow_pay_points');
|
||||
delete_post_meta($post_id, '_aksrt_follow_pay_vip_price');
|
||||
delete_post_meta($post_id, '_aksrt_follow_pay_vip1_price');
|
||||
delete_post_meta($post_id, '_aksrt_follow_pay_vip2_price');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -399,16 +406,25 @@ class AKSRT_Plate_Meta_Box {
|
||||
update_post_meta($post_id, '_aksrt_follow_pay_price', $price);
|
||||
update_post_meta($post_id, '_aksrt_follow_pay_points', $points);
|
||||
|
||||
if (isset($_POST['aksrt_plate_follow_pay_vip_price']) && (string) wp_unslash($_POST['aksrt_plate_follow_pay_vip_price']) !== '') {
|
||||
$vip = max(0, (float) wp_unslash($_POST['aksrt_plate_follow_pay_vip_price']));
|
||||
update_post_meta($post_id, '_aksrt_follow_pay_vip_price', $vip);
|
||||
if (isset($_POST['aksrt_plate_follow_pay_vip1_price']) && (string) wp_unslash($_POST['aksrt_plate_follow_pay_vip1_price']) !== '') {
|
||||
$vip1 = max(0, (float) wp_unslash($_POST['aksrt_plate_follow_pay_vip1_price']));
|
||||
update_post_meta($post_id, '_aksrt_follow_pay_vip1_price', $vip1);
|
||||
// Keep legacy single VIP key in sync with VIP1 for old readers.
|
||||
update_post_meta($post_id, '_aksrt_follow_pay_vip_price', $vip1);
|
||||
} else {
|
||||
delete_post_meta($post_id, '_aksrt_follow_pay_vip1_price');
|
||||
delete_post_meta($post_id, '_aksrt_follow_pay_vip_price');
|
||||
}
|
||||
|
||||
if (isset($_POST['aksrt_plate_follow_pay_vip2_price']) && (string) wp_unslash($_POST['aksrt_plate_follow_pay_vip2_price']) !== '') {
|
||||
update_post_meta($post_id, '_aksrt_follow_pay_vip2_price', max(0, (float) wp_unslash($_POST['aksrt_plate_follow_pay_vip2_price'])));
|
||||
} else {
|
||||
delete_post_meta($post_id, '_aksrt_follow_pay_vip2_price');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{enabled:bool,modo:string,price:float,points:int,vip_price:string}
|
||||
* @return array{enabled:bool,modo:string,price:float,points:int,vip1_price:string,vip2_price:string}
|
||||
*/
|
||||
private function get_follow_pay_meta(int $plate_id): array {
|
||||
$enabled = (int) get_post_meta($plate_id, '_aksrt_follow_pay', true) === 1;
|
||||
@@ -416,15 +432,25 @@ class AKSRT_Plate_Meta_Box {
|
||||
if (!in_array($modo, ['money', 'points'], true)) {
|
||||
$modo = 'money';
|
||||
}
|
||||
$vip_raw = get_post_meta($plate_id, '_aksrt_follow_pay_vip_price', true);
|
||||
$vip_set = ($vip_raw !== '' && $vip_raw !== null && $vip_raw !== false);
|
||||
|
||||
$vip1_raw = get_post_meta($plate_id, '_aksrt_follow_pay_vip1_price', true);
|
||||
$vip1_set = ($vip1_raw !== '' && $vip1_raw !== null && $vip1_raw !== false);
|
||||
if (!$vip1_set) {
|
||||
// Legacy single VIP key → vip1.
|
||||
$vip1_raw = get_post_meta($plate_id, '_aksrt_follow_pay_vip_price', true);
|
||||
$vip1_set = ($vip1_raw !== '' && $vip1_raw !== null && $vip1_raw !== false);
|
||||
}
|
||||
|
||||
$vip2_raw = get_post_meta($plate_id, '_aksrt_follow_pay_vip2_price', true);
|
||||
$vip2_set = ($vip2_raw !== '' && $vip2_raw !== null && $vip2_raw !== false);
|
||||
|
||||
return [
|
||||
'enabled' => $enabled,
|
||||
'modo' => $modo,
|
||||
'price' => (float) get_post_meta($plate_id, '_aksrt_follow_pay_price', true),
|
||||
'points' => (int) get_post_meta($plate_id, '_aksrt_follow_pay_points', true),
|
||||
'vip_price' => $vip_set ? (string) (float) $vip_raw : '',
|
||||
'enabled' => $enabled,
|
||||
'modo' => $modo,
|
||||
'price' => (float) get_post_meta($plate_id, '_aksrt_follow_pay_price', true),
|
||||
'points' => (int) get_post_meta($plate_id, '_aksrt_follow_pay_points', true),
|
||||
'vip1_price' => $vip1_set ? (string) (float) $vip1_raw : '',
|
||||
'vip2_price' => $vip2_set ? (string) (float) $vip2_raw : '',
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user