diff --git a/docs/zibll-gap-matrix.md b/docs/zibll-gap-matrix.md index 39fb4870..901d49a2 100644 --- a/docs/zibll-gap-matrix.md +++ b/docs/zibll-gap-matrix.md @@ -100,7 +100,7 @@ Note: zibll「商品管理」`zibpay_product_page` is **paid article sales**, no | 3 users.php submenus | done | | 4 SPA strip | done (ops routes redirect to native) | | 5+ behavior parity | done — native ops shell + plate follow-pay + topic pay matrix + multi-gateway topic cashier (`AKSRT_Topic_Pay` /pay/topic) + topic coupon burn (P1) + plate follow VIP1/2 + multi-gateway (`AKSRT_Plate_Follow_Pay` /pay/plate-follow) + cash creator revenue (content/topic/plate) + sales report/download logs + hidecontent + users commerce + order filters/clear-closed + post pay_type filter + bulk content-pay edit + income/rebate user filter | -| 5 remaining | optional polish only (guest topic/plate purchase — out of scope); plate follow coupons + free auto-order + note JSON preserve + shared coupon burn done | +| 5 remaining | optional polish only; guest topic/plate cash purchase + cookie unlock done (reuses pay_no_logged_in / pay_cookie_day) | ## Dual-UI rule diff --git a/inc/classes/class-aksrt-content-pay.php b/inc/classes/class-aksrt-content-pay.php index ae890b79..e6eb347d 100644 --- a/inc/classes/class-aksrt-content-pay.php +++ b/inc/classes/class-aksrt-content-pay.php @@ -159,9 +159,13 @@ class AKSRT_Content_Pay { $order = $repo->get_user_order($uid, $order_number); } else { $order = $repo->get($order_number); - // Guest may only poll own guest content orders (user_id=0 + resource). - if ($order && ((int) ($order['user_id'] ?? -1) !== 0 - || (string) ($order['product_type'] ?? '') !== AKSRT_Domain_Model::ORDER_PRODUCT_RESOURCE)) { + // Guest may only poll own guest cash orders (user_id=0): content/topic resource or plate_follow. + $ptype = sanitize_key((string) ($order['product_type'] ?? '')); + $guest_types = [ + AKSRT_Domain_Model::ORDER_PRODUCT_RESOURCE, + AKSRT_Domain_Model::ORDER_PRODUCT_PLATE_FOLLOW, + ]; + if ($order && ((int) ($order['user_id'] ?? -1) !== 0 || !in_array($ptype, $guest_types, true))) { $order = null; } } @@ -170,9 +174,9 @@ class AKSRT_Content_Pay { } $status = (string) ($order['status'] ?? ''); $paid = class_exists('AKSRT_Domain_Model') && AKSRT_Domain_Model::is_paid_order_status($status); - if ($paid && $uid <= 0 && function_exists('aksrt_set_guest_content_pay_cookies')) { + if ($paid && $uid <= 0 && function_exists('aksrt_set_guest_pay_cookies')) { // Ensure cookie set even if notify raced ahead of browser cookie. - aksrt_set_guest_content_pay_cookies($order); + aksrt_set_guest_pay_cookies($order); } return new WP_REST_Response([ 'order_number' => $order_number, diff --git a/inc/classes/class-aksrt-plate-follow-pay.php b/inc/classes/class-aksrt-plate-follow-pay.php index b39ba640..10aebe74 100644 --- a/inc/classes/class-aksrt-plate-follow-pay.php +++ b/inc/classes/class-aksrt-plate-follow-pay.php @@ -73,8 +73,9 @@ class AKSRT_Plate_Follow_Pay { public function rest_purchase(WP_REST_Request $request) { $plate_id = (int) $request['id']; $uid = get_current_user_id(); - if ($uid <= 0) { - $post = get_post($plate_id); + $post = get_post($plate_id); + $allow_guest = function_exists('aksrt_allow_guest_forum_pay') && aksrt_allow_guest_forum_pay($post); + if ($uid <= 0 && !$allow_guest) { return new WP_Error('aksrt_need_login', __('请先登录后再关注。', 'aksrt'), [ 'status' => 401, 'login_url' => wp_login_url($post ? get_permalink($post) : home_url('/')), @@ -90,6 +91,9 @@ class AKSRT_Plate_Follow_Pay { if ($payment_method === '' || $payment_method === 'cash') { $payment_method = 'balance'; } + if ($uid <= 0 && in_array($payment_method, ['balance', 'points'], true)) { + $payment_method = 'alipay'; + } $coupon_code = sanitize_text_field((string) ($params['coupon_code'] ?? $params['coupon'] ?? '')); return $this->rest_wrap_result( @@ -132,8 +136,9 @@ class AKSRT_Plate_Follow_Pay { $post = $assert['post']; $uid = $user_id > 0 ? $user_id : get_current_user_id(); $coupon_code = sanitize_text_field($coupon_code); + $allow_guest = function_exists('aksrt_allow_guest_forum_pay') && aksrt_allow_guest_forum_pay($post); - if ($uid <= 0) { + if ($uid <= 0 && !$allow_guest) { return [ 'post_id' => $plate_id, 'title' => get_the_title($post), @@ -185,16 +190,21 @@ class AKSRT_Plate_Follow_Pay { $list_amount = round((float) ($priced['amount'] ?? 0), 2); $base = round((float) ($priced['base'] ?? $list_amount), 2); $vip_free = !empty($priced['is_free']) || ($list_amount <= 0 && !empty($priced['is_vip_price'])); + if ($uid <= 0) { + $vip_free = false; + } $has_followed = $this->user_has_followed($uid, $plate_id); $balance = 0; - if (class_exists('AKSRT_Credits')) { + if ($uid > 0 && class_exists('AKSRT_Credits')) { $balance = (int) AKSRT_Credits::balance($uid); } $methods = function_exists('aksrt_list_cash_payment_methods') - ? aksrt_list_cash_payment_methods(true) - : [['id' => 'balance', 'name' => __('站内余额', 'aksrt')]]; + ? aksrt_list_cash_payment_methods($uid > 0) + : ($uid > 0 + ? [['id' => 'balance', 'name' => __('站内余额', 'aksrt')]] + : [['id' => 'alipay', 'name' => __('支付宝', 'aksrt')]]); $coupon_allowed = $this->is_coupon_allowed($plate_id); $coupon_error = ''; @@ -250,11 +260,11 @@ class AKSRT_Plate_Follow_Pay { 'has_paid_access' => $has_followed, 'paid_order' => null, 'pending_order' => null, - 'need_login' => false, - 'allow_guest' => false, + 'need_login' => $uid <= 0 && !$allow_guest, + 'allow_guest' => $allow_guest, 'need_vip' => false, 'need_purchase' => !$has_followed && !$is_free, - 'logged_in' => true, + 'logged_in' => $uid > 0, 'balance' => $balance, 'balance_enough' => true, 'payment_methods' => $methods, @@ -284,10 +294,6 @@ class AKSRT_Plate_Follow_Pay { * @return array|WP_Error */ public function create_cash_order(int $plate_id, int $user_id, string $payment_method = 'balance', string $coupon_code = '') { - if ($user_id <= 0) { - return new WP_Error('aksrt_need_login', __('请先登录后再关注。', 'aksrt'), ['status' => 401]); - } - $assert = $this->assert_plate($plate_id); if (is_wp_error($assert)) { return $assert; @@ -295,8 +301,19 @@ class AKSRT_Plate_Follow_Pay { /** @var WP_Post $post */ $post = $assert['post']; + $allow_guest = function_exists('aksrt_allow_guest_forum_pay') && aksrt_allow_guest_forum_pay($post); + if ($user_id <= 0 && !$allow_guest) { + return new WP_Error('aksrt_need_login', __('请先登录后再关注。', 'aksrt'), [ + 'status' => 401, + 'login_url' => wp_login_url(get_permalink($post)), + ]); + } + if ($this->user_has_followed($user_id, $plate_id)) { - return new WP_Error('aksrt_already_followed', __('已关注该版块。', 'aksrt'), ['status' => 400]); + return new WP_Error('aksrt_already_followed', __('已关注该版块。', 'aksrt'), [ + 'status' => 400, + 'reload' => true, + ]); } $priced = $this->resolve_price($plate_id, $user_id); @@ -307,9 +324,12 @@ class AKSRT_Plate_Follow_Pay { $list_amount = round((float) ($priced['amount'] ?? 0), 2); $base = round((float) ($priced['base'] ?? $list_amount), 2); $vip_free = !empty($priced['is_free']) || ($list_amount <= 0 && !empty($priced['is_vip_price'])); + if ($user_id <= 0) { + $vip_free = false; + } $coupon_code = sanitize_text_field($coupon_code); - // VIP free — no coupon stack. + // VIP free — no coupon stack. Guests never hit this. if ($vip_free) { return $this->create_free_paid_order($plate_id, $user_id, $post, $priced, [ 'list_amount' => 0.0, @@ -356,7 +376,10 @@ class AKSRT_Plate_Follow_Pay { $payment_method = sanitize_key($payment_method); if ($payment_method === '') { - $payment_method = 'balance'; + $payment_method = $user_id > 0 ? 'balance' : 'alipay'; + } + if ($user_id <= 0 && in_array($payment_method, ['balance', 'points'], true)) { + $payment_method = 'alipay'; } $pending = $this->find_pending_order($user_id, $plate_id); @@ -388,11 +411,12 @@ class AKSRT_Plate_Follow_Pay { 'coupon_code' => $coupon_code_norm, 'coupon_discount' => $coupon_discount, 'coupon' => $coupon_meta, + 'guest' => $user_id <= 0 ? 1 : 0, 'ip_address' => function_exists('aksrt_pay_client_ip') ? aksrt_pay_client_ip() : (string) ($_SERVER['REMOTE_ADDR'] ?? ''), ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); $order_number = AKSRT_Order_Repository::get_instance()->create([ - 'user_id' => $user_id, + 'user_id' => max(0, $user_id), 'product_type' => AKSRT_Domain_Model::ORDER_PRODUCT_PLATE_FOLLOW, 'product_id' => $plate_id, 'amount' => $amount, @@ -480,7 +504,9 @@ class AKSRT_Plate_Follow_Pay { return new WP_Error('aksrt_order_confirm_failed', __('订单确认失败。', 'aksrt'), ['status' => 500]); } - $this->mark_followed($user_id, $plate_id); + if ($user_id > 0) { + $this->mark_followed($user_id, $plate_id); + } return [ 'success' => true, @@ -502,7 +528,7 @@ class AKSRT_Plate_Follow_Pay { $plate_id = (int) ($paid_order['product_id'] ?? 0); $user_id = (int) ($paid_order['user_id'] ?? 0); - if ($plate_id <= 0 || $user_id <= 0) { + if ($plate_id <= 0) { return; } @@ -514,7 +540,17 @@ class AKSRT_Plate_Follow_Pay { } } - $this->mark_followed($user_id, $plate_id); + if ($user_id > 0) { + $this->mark_followed($user_id, $plate_id); + } elseif (function_exists('aksrt_set_guest_pay_cookies')) { + if (class_exists('AKSRT_Order_Repository')) { + $full = AKSRT_Order_Repository::get_instance()->get($order_number); + if (is_array($full)) { + $paid_order = array_merge($full, $paid_order); + } + } + aksrt_set_guest_pay_cookies($paid_order); + } $this->finalize_plate_order_side_effects($order_number, $paid_order); } @@ -568,9 +604,13 @@ class AKSRT_Plate_Follow_Pay { } private function user_has_followed(int $user_id, int $plate_id): bool { - if ($user_id <= 0 || $plate_id <= 0) { + if ($plate_id <= 0) { return false; } + if ($user_id <= 0) { + return function_exists('aksrt_guest_has_plate_follow_access') + && aksrt_guest_has_plate_follow_access($plate_id); + } if (class_exists('AKSRT_Forum') && method_exists('AKSRT_Forum', 'is_user_following_plate')) { return (bool) AKSRT_Forum::get_instance()->is_user_following_plate($user_id, $plate_id); } @@ -648,11 +688,12 @@ class AKSRT_Plate_Follow_Pay { 'coupon_discount' => $coupon_discount, 'coupon' => $coupon_meta, 'free' => 1, + 'guest' => $user_id <= 0 ? 1 : 0, 'ip_address' => function_exists('aksrt_pay_client_ip') ? aksrt_pay_client_ip() : (string) ($_SERVER['REMOTE_ADDR'] ?? ''), ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); $order_number = AKSRT_Order_Repository::get_instance()->create([ - 'user_id' => $user_id, + 'user_id' => max(0, $user_id), 'product_type' => AKSRT_Domain_Model::ORDER_PRODUCT_PLATE_FOLLOW, 'product_id' => $plate_id, 'amount' => 0.0, diff --git a/inc/classes/class-aksrt-topic-pay.php b/inc/classes/class-aksrt-topic-pay.php index b7dcb768..18f45522 100644 --- a/inc/classes/class-aksrt-topic-pay.php +++ b/inc/classes/class-aksrt-topic-pay.php @@ -74,8 +74,9 @@ class AKSRT_Topic_Pay { public function rest_purchase(WP_REST_Request $request) { $topic_id = (int) $request['id']; $uid = get_current_user_id(); - if ($uid <= 0) { - $post = get_post($topic_id); + $post = get_post($topic_id); + $allow_guest = function_exists('aksrt_allow_guest_forum_pay') && aksrt_allow_guest_forum_pay($post); + if ($uid <= 0 && !$allow_guest) { return new WP_Error('aksrt_need_login', __('请先登录后再购买。', 'aksrt'), [ 'status' => 401, 'login_url' => wp_login_url($post ? get_permalink($post) : home_url('/')), @@ -91,6 +92,10 @@ class AKSRT_Topic_Pay { if ($payment_method === '' || $payment_method === 'cash') { $payment_method = 'balance'; } + // Guests cannot use balance. + if ($uid <= 0 && in_array($payment_method, ['balance', 'points'], true)) { + $payment_method = 'alipay'; + } $coupon_code = sanitize_text_field((string) ($params['coupon_code'] ?? $params['coupon'] ?? '')); return $this->rest_wrap_result( @@ -135,8 +140,9 @@ class AKSRT_Topic_Pay { $post = $assert['post']; $uid = $user_id > 0 ? $user_id : get_current_user_id(); $coupon_code = sanitize_text_field($coupon_code); + $allow_guest = function_exists('aksrt_allow_guest_forum_pay') && aksrt_allow_guest_forum_pay($post); - if ($uid <= 0) { + if ($uid <= 0 && !$allow_guest) { return [ 'post_id' => $topic_id, 'title' => get_the_title($post), @@ -184,16 +190,22 @@ class AKSRT_Topic_Pay { $list_amount = round((float) ($priced['amount'] ?? 0), 2); $base = round((float) ($priced['base'] ?? $list_amount), 2); $vip_free = !empty($priced['is_free']) || ($list_amount <= 0 && !empty($priced['is_vip_price'])); + // Guests never get VIP free (vip_level=0). + if ($uid <= 0) { + $vip_free = false; + } $has_paid = $this->user_has_paid($uid, $topic_id); $balance = 0; - if (class_exists('AKSRT_Credits')) { + if ($uid > 0 && class_exists('AKSRT_Credits')) { $balance = (int) AKSRT_Credits::balance($uid); } $methods = function_exists('aksrt_list_cash_payment_methods') - ? aksrt_list_cash_payment_methods(true) - : [['id' => 'balance', 'name' => __('站内余额', 'aksrt')]]; + ? aksrt_list_cash_payment_methods($uid > 0) + : ($uid > 0 + ? [['id' => 'balance', 'name' => __('站内余额', 'aksrt')]] + : [['id' => 'alipay', 'name' => __('支付宝', 'aksrt')]]); $coupon_allowed = $this->is_coupon_allowed($topic_id); $coupon_error = ''; @@ -249,11 +261,11 @@ class AKSRT_Topic_Pay { 'has_paid_access' => $has_paid, 'paid_order' => null, 'pending_order' => null, - 'need_login' => false, - 'allow_guest' => false, + 'need_login' => $uid <= 0 && !$allow_guest, + 'allow_guest' => $allow_guest, 'need_vip' => false, 'need_purchase' => !$has_paid && !$is_free, - 'logged_in' => true, + 'logged_in' => $uid > 0, 'balance' => $balance, 'balance_enough' => true, 'payment_methods' => $methods, @@ -285,10 +297,6 @@ class AKSRT_Topic_Pay { * @return array|WP_Error */ public function create_cash_order(int $topic_id, int $user_id, string $payment_method = 'balance', string $coupon_code = '') { - if ($user_id <= 0) { - return new WP_Error('aksrt_need_login', __('请先登录后再购买。', 'aksrt'), ['status' => 401]); - } - $assert = $this->assert_topic($topic_id); if (is_wp_error($assert)) { return $assert; @@ -296,17 +304,32 @@ class AKSRT_Topic_Pay { /** @var WP_Post $post */ $post = $assert['post']; + $allow_guest = function_exists('aksrt_allow_guest_forum_pay') && aksrt_allow_guest_forum_pay($post); + if ($user_id <= 0 && !$allow_guest) { + return new WP_Error('aksrt_need_login', __('请先登录后再购买。', 'aksrt'), [ + 'status' => 401, + 'login_url' => wp_login_url(get_permalink($post)), + ]); + } + if ($this->user_has_paid($user_id, $topic_id)) { - return new WP_Error('aksrt_already_paid', __('已购买,可直接查看。', 'aksrt'), ['status' => 400]); + return new WP_Error('aksrt_already_paid', __('已购买,可直接查看。', 'aksrt'), [ + 'status' => 400, + 'has_paid_access' => true, + 'reload' => true, + ]); } $priced = $this->resolve_price($topic_id, $user_id); $list_amount = round((float) ($priced['amount'] ?? 0), 2); $base = round((float) ($priced['base'] ?? $list_amount), 2); $vip_free = !empty($priced['is_free']) || ($list_amount <= 0 && !empty($priced['is_vip_price'])); + if ($user_id <= 0) { + $vip_free = false; + } $coupon_code = sanitize_text_field($coupon_code); - // VIP free (no purchase price) — no coupon stack. + // VIP free (no purchase price) — no coupon stack. Guests never hit this. if ($vip_free) { return $this->create_free_paid_order($topic_id, $user_id, $post, $priced, [ 'list_amount' => 0.0, @@ -353,7 +376,10 @@ class AKSRT_Topic_Pay { $payment_method = sanitize_key($payment_method); if ($payment_method === '') { - $payment_method = 'balance'; + $payment_method = $user_id > 0 ? 'balance' : 'alipay'; + } + if ($user_id <= 0 && in_array($payment_method, ['balance', 'points'], true)) { + $payment_method = 'alipay'; } $pending = $this->find_pending_topic_order($user_id, $topic_id); @@ -385,11 +411,12 @@ class AKSRT_Topic_Pay { 'coupon_code' => $coupon_code_norm, 'coupon_discount' => $coupon_discount, 'coupon' => $coupon_meta, + 'guest' => $user_id <= 0 ? 1 : 0, 'ip_address' => function_exists('aksrt_pay_client_ip') ? aksrt_pay_client_ip() : (string) ($_SERVER['REMOTE_ADDR'] ?? ''), ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); $order_number = AKSRT_Order_Repository::get_instance()->create([ - 'user_id' => $user_id, + 'user_id' => max(0, $user_id), 'product_type' => AKSRT_Domain_Model::ORDER_PRODUCT_RESOURCE, 'product_id' => $topic_id, 'amount' => $amount, @@ -478,7 +505,9 @@ class AKSRT_Topic_Pay { return new WP_Error('aksrt_order_confirm_failed', __('订单确认失败。', 'aksrt'), ['status' => 500]); } - $this->mark_unlocked($user_id, $topic_id); + if ($user_id > 0) { + $this->mark_unlocked($user_id, $topic_id); + } return [ 'success' => true, @@ -493,7 +522,7 @@ class AKSRT_Topic_Pay { } /** - * On paid: unlock topic + burn coupon (idempotent). + * On paid: unlock topic (or guest cookie) + burn coupon (idempotent). */ public function on_order_paid(string $order_number, array $paid_order): void { $product_type = sanitize_key((string) ($paid_order['product_type'] ?? '')); @@ -503,7 +532,7 @@ class AKSRT_Topic_Pay { $topic_id = (int) ($paid_order['product_id'] ?? 0); $user_id = (int) ($paid_order['user_id'] ?? 0); - if ($topic_id <= 0 || $user_id <= 0) { + if ($topic_id <= 0) { return; } @@ -516,7 +545,17 @@ class AKSRT_Topic_Pay { } } - $this->mark_unlocked($user_id, $topic_id); + if ($user_id > 0) { + $this->mark_unlocked($user_id, $topic_id); + } elseif (function_exists('aksrt_set_guest_pay_cookies')) { + if (class_exists('AKSRT_Order_Repository')) { + $full = AKSRT_Order_Repository::get_instance()->get($order_number); + if (is_array($full)) { + $paid_order = array_merge($full, $paid_order); + } + } + aksrt_set_guest_pay_cookies($paid_order); + } $this->finalize_topic_order_side_effects($order_number, $paid_order); } @@ -559,6 +598,9 @@ class AKSRT_Topic_Pay { } private function user_has_paid(int $user_id, int $topic_id): bool { + if ($user_id <= 0) { + return function_exists('aksrt_guest_has_paid_access') && aksrt_guest_has_paid_access($topic_id); + } if (class_exists('AKSRT_Forum')) { return AKSRT_Forum::get_instance()->has_user_paid_topic($user_id, $topic_id); } @@ -736,11 +778,12 @@ class AKSRT_Topic_Pay { 'coupon_discount' => $coupon_discount, 'coupon' => $coupon_meta, 'free' => 1, + 'guest' => $user_id <= 0 ? 1 : 0, 'ip_address' => function_exists('aksrt_pay_client_ip') ? aksrt_pay_client_ip() : (string) ($_SERVER['REMOTE_ADDR'] ?? ''), ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); $order_number = AKSRT_Order_Repository::get_instance()->create([ - 'user_id' => $user_id, + 'user_id' => max(0, $user_id), 'product_type' => AKSRT_Domain_Model::ORDER_PRODUCT_RESOURCE, 'product_id' => $topic_id, 'amount' => 0.0, diff --git a/inc/classes/forum/board-service-traits/trait-aksrt-forum-board-service-part-2.php b/inc/classes/forum/board-service-traits/trait-aksrt-forum-board-service-part-2.php index 71da4dd5..51b0852c 100644 --- a/inc/classes/forum/board-service-traits/trait-aksrt-forum-board-service-part-2.php +++ b/inc/classes/forum/board-service-traits/trait-aksrt-forum-board-service-part-2.php @@ -209,6 +209,13 @@ trait AKSRT_Forum_Board_Service_Part_2 { } if ($user_id <= 0) { + // Guest cash follow unlock via signed cookie (money follow pay only). + if ($allow_view === 'follow' + && function_exists('aksrt_guest_has_plate_follow_access') + && aksrt_guest_has_plate_follow_access($plate_id) + ) { + return true; + } return false; } @@ -282,9 +289,13 @@ trait AKSRT_Forum_Board_Service_Part_2 { * Check if a user is following a plate (§7.2 关注). */ public function is_user_following_plate(int $user_id, int $plate_id): bool { - if ($user_id <= 0) { + if ($plate_id <= 0) { return false; } + if ($user_id <= 0) { + return function_exists('aksrt_guest_has_plate_follow_access') + && aksrt_guest_has_plate_follow_access($plate_id); + } $follow_list = get_user_meta($user_id, 'aksrt_follow_plate', true); if (!is_array($follow_list)) { return false; diff --git a/inc/classes/forum/topic-service-traits/trait-aksrt-forum-topic-service-part-3.php b/inc/classes/forum/topic-service-traits/trait-aksrt-forum-topic-service-part-3.php index 6cc460fa..4ebb855e 100644 --- a/inc/classes/forum/topic-service-traits/trait-aksrt-forum-topic-service-part-3.php +++ b/inc/classes/forum/topic-service-traits/trait-aksrt-forum-topic-service-part-3.php @@ -220,7 +220,8 @@ trait AKSRT_Forum_Topic_Service_Part_3 { if ($visibility === AKSRT_Forum::VISIBILITY_PAY) { if ($viewer_user_id <= 0) { - return false; + return function_exists('aksrt_guest_has_paid_access') + && aksrt_guest_has_paid_access((int) $post->ID); } return $this->has_user_paid_topic($viewer_user_id, (int) $post->ID); } @@ -514,9 +515,12 @@ trait AKSRT_Forum_Topic_Service_Part_3 { * Paid access for VISIBILITY_PAY: unlock list OR paid resource order OR VIP free. */ public function has_user_paid_topic(int $user_id, int $topic_id): bool { - if ($user_id <= 0 || $topic_id <= 0) { + if ($topic_id <= 0) { return false; } + if ($user_id <= 0) { + return function_exists('aksrt_guest_has_paid_access') && aksrt_guest_has_paid_access($topic_id); + } if ($this->has_user_unlocked_topic($user_id, $topic_id)) { return true; diff --git a/inc/helpers/aksrt-pay.php b/inc/helpers/aksrt-pay.php index 8b23a867..80de0f2a 100644 --- a/inc/helpers/aksrt-pay.php +++ b/inc/helpers/aksrt-pay.php @@ -361,12 +361,31 @@ if (!function_exists('aksrt_content_order_cookie_token_key')) { } /** - * Set guest paid cookies after content order paid (Zibll zibpay_posts_order_created_set_cookie). + * Whether guest cash purchase is allowed for forum topic / plate follow. + * Reuses global pay_no_logged_in (same switch as content). + * + * @param WP_Post|int|null $post Topic or plate post (optional filter context). + */ +if (!function_exists('aksrt_allow_guest_forum_pay')) { + function aksrt_allow_guest_forum_pay($post = null): bool { + $order = class_exists('AKSRT_Settings') ? AKSRT_Settings::get_order_settings() : []; + $paid = class_exists('AKSRT_Settings') ? AKSRT_Settings::get_paid_content_settings() : []; + $allow = !empty($order['no_logged_in']) || !empty($paid['no_logged_in']); + /** + * @param bool $allow + * @param WP_Post|int|null $post + */ + return (bool) apply_filters('aksrt_allow_guest_forum_pay', $allow, $post); + } +} + +/** + * Set guest paid cookies after cash order paid (content / topic / plate_follow). * * @param array $order */ -if (!function_exists('aksrt_set_guest_content_pay_cookies')) { - function aksrt_set_guest_content_pay_cookies(array $order): void { +if (!function_exists('aksrt_set_guest_pay_cookies')) { + function aksrt_set_guest_pay_cookies(array $order): void { $post_id = (int) ($order['product_id'] ?? 0); $order_number = (string) ($order['order_number'] ?? ''); if ($post_id <= 0 || $order_number === '') { @@ -390,19 +409,30 @@ if (!function_exists('aksrt_set_guest_content_pay_cookies')) { } /** - * Resolve guest paid order from browser cookie for a post. + * Set guest paid cookies after content order paid (Zibll zibpay_posts_order_created_set_cookie). + * + * @param array $order + */ +if (!function_exists('aksrt_set_guest_content_pay_cookies')) { + function aksrt_set_guest_content_pay_cookies(array $order): void { + aksrt_set_guest_pay_cookies($order); + } +} + +/** + * Resolve guest paid order from browser cookie for a product. * * @return array|null */ -if (!function_exists('aksrt_get_guest_content_paid_order')) { - function aksrt_get_guest_content_paid_order(int $post_id): ?array { - if ($post_id <= 0 || empty($_COOKIE['aksrt_pay_' . $post_id])) { +if (!function_exists('aksrt_get_guest_paid_order')) { + function aksrt_get_guest_paid_order(int $product_id, string $product_type = ''): ?array { + if ($product_id <= 0 || empty($_COOKIE['aksrt_pay_' . $product_id])) { return null; } - if (!class_exists('AKSRT_Order_Repository')) { + if (!class_exists('AKSRT_Order_Repository') || !class_exists('AKSRT_Domain_Model')) { return null; } - $order_number = sanitize_text_field((string) $_COOKIE['aksrt_pay_' . $post_id]); + $order_number = sanitize_text_field((string) $_COOKIE['aksrt_pay_' . $product_id]); if ($order_number === '') { return null; } @@ -410,10 +440,16 @@ if (!function_exists('aksrt_get_guest_content_paid_order')) { if (!$order) { return null; } - if ((string) ($order['product_type'] ?? '') !== AKSRT_Domain_Model::ORDER_PRODUCT_RESOURCE) { + $ptype = sanitize_key((string) ($order['product_type'] ?? '')); + $want = sanitize_key($product_type); + if ($want !== '' && $ptype !== $want) { return null; } - if ((int) ($order['product_id'] ?? 0) !== $post_id) { + if ((int) ($order['product_id'] ?? 0) !== $product_id) { + return null; + } + // Guest orders only (logged-in users use user_id access). + if ((int) ($order['user_id'] ?? -1) !== 0) { return null; } $status = (string) ($order['status'] ?? ''); @@ -421,11 +457,10 @@ if (!function_exists('aksrt_get_guest_content_paid_order')) { return null; } - // Cookie day window from paid_at. $paid_at = (string) ($order['paid_at'] ?? $order['created_at'] ?? ''); if ($paid_at !== '') { $ts = strtotime($paid_at); - $days = aksrt_guest_pay_cookie_days($post_id); + $days = aksrt_guest_pay_cookie_days($product_id); if ($ts !== false && ($ts + DAY_IN_SECONDS * $days) < time()) { return null; } @@ -443,7 +478,21 @@ if (!function_exists('aksrt_get_guest_content_paid_order')) { } /** - * Whether current request has paid access via guest cookie. + * Resolve guest paid order from browser cookie for a post (content/resource). + * + * @return array|null + */ +if (!function_exists('aksrt_get_guest_content_paid_order')) { + function aksrt_get_guest_content_paid_order(int $post_id): ?array { + $type = class_exists('AKSRT_Domain_Model') + ? AKSRT_Domain_Model::ORDER_PRODUCT_RESOURCE + : 'resource'; + return aksrt_get_guest_paid_order($post_id, $type); + } +} + +/** + * Whether current request has paid access via guest cookie (content/resource product). */ if (!function_exists('aksrt_guest_has_paid_access')) { function aksrt_guest_has_paid_access(int $post_id): bool { @@ -451,6 +500,18 @@ if (!function_exists('aksrt_guest_has_paid_access')) { } } +/** + * Guest paid plate follow access (cookie). + */ +if (!function_exists('aksrt_guest_has_plate_follow_access')) { + function aksrt_guest_has_plate_follow_access(int $plate_id): bool { + $type = class_exists('AKSRT_Domain_Model') + ? AKSRT_Domain_Model::ORDER_PRODUCT_PLATE_FOLLOW + : 'plate_follow'; + return null !== aksrt_get_guest_paid_order($plate_id, $type); + } +} + /** * Resolve the amount a given user should pay for a post (Zibll VIP tier min). * diff --git a/templates/forum/forum-plate.php b/templates/forum/forum-plate.php index 3e787bb7..591bbe55 100644 --- a/templates/forum/forum-plate.php +++ b/templates/forum/forum-plate.php @@ -106,6 +106,20 @@ $max_pages = (int) ($view['pagination']['max_pages'] ?? 1);
+ is_plate_follow_pay_enabled($plate_id) + : ((int) get_post_meta($plate_id, '_aksrt_follow_pay', true) === 1); + $follow_pay_modo = sanitize_key((string) (get_post_meta($plate_id, '_aksrt_follow_pay_modo', true) ?: 'money')); + $allow_guest_plate = $current_user_id <= 0 + && $follow_pay_on + && $follow_pay_modo === 'money' + && function_exists('aksrt_allow_guest_forum_pay') + && aksrt_allow_guest_forum_pay(get_post($plate_id)); + $guest_followed = $current_user_id <= 0 + && function_exists('aksrt_guest_has_plate_follow_access') + && aksrt_guest_has_plate_follow_access($plate_id); + ?> 0) : ?> + + + + +

+ +

diff --git a/templates/forum/forum-topic.php b/templates/forum/forum-topic.php index a86becae..1728b735 100644 --- a/templates/forum/forum-topic.php +++ b/templates/forum/forum-topic.php @@ -192,7 +192,12 @@ if (empty($topic['can_view'])) : 0 || $base_amount > 0) : ?> - + + + +

+ + +

+