refactor(classes): organize inc/classes into domain folders
Move ~94 root class shells and root *-traits into domain directories (core, auth, user, credits, content, payment/gateways, forum, shop, sms, ai, messages, extensions, widgets, settings, admin). Update bootstrap require paths, co-locate traits with owners, fix forum/user relative requires, drop duplicate shop controller requires, and wire home-modules load. Path-only; class names and init order unchanged.
This commit is contained in:
1 parent
2a8fe68048
commit
ce1c13b947
115 files changed
+171
-224
No files matched your search
@@ -0,0 +1,872 @@
|
||||
<?php
|
||||
/**
|
||||
* Forum domain service.
|
||||
*
|
||||
* Keeps the canonical forum model on top of:
|
||||
* - post type: aksrt_forum_topic
|
||||
* - board taxonomy: aksrt_forum_board
|
||||
* - tag taxonomy: aksrt_forum_tag
|
||||
* - governance data: post meta / term meta
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class AKSRT_Forum {
|
||||
|
||||
private static $instance = null;
|
||||
|
||||
const TIER_NONE = 0; // 游客/无权
|
||||
const TIER_MODERATOR = 1; // 版主(plate meta _aksrt_moderators)
|
||||
const TIER_PLATE_AUTHOR = 2; // 超级版主(plate post_author)
|
||||
const TIER_CAT_MOD = 3; // 分区版主(plate_cat term meta moderator)
|
||||
const TIER_FORUM_ADMIN = 4; // 论坛管理员(bbs_admin_users + manage_options)
|
||||
|
||||
private static array $cap_cache = [];
|
||||
private static array $tier_cache = [];
|
||||
|
||||
private ?AKSRT_Forum_Permission_Service $perm_service = null;
|
||||
|
||||
private ?AKSRT_Forum_Score_Service $score_service = null;
|
||||
|
||||
private ?AKSRT_Forum_Comment_Service $comment_service = null;
|
||||
|
||||
private ?AKSRT_Forum_Draft_Service $draft_svc = null;
|
||||
|
||||
private ?AKSRT_Forum_Board_Service $board_svc = null;
|
||||
|
||||
const VISIBILITY_PUBLIC = 'public';
|
||||
const VISIBILITY_LOGIN = 'login';
|
||||
const VISIBILITY_COMMENT = 'comment';
|
||||
const VISIBILITY_ROLE = 'role';
|
||||
const VISIBILITY_CREDITS = 'credits';
|
||||
const VISIBILITY_PAY = 'pay';
|
||||
const VISIBILITY_MODERATOR = 'moderator';
|
||||
|
||||
const REVIEW_APPROVED = 'approved';
|
||||
const REVIEW_PENDING = 'pending';
|
||||
const REVIEW_REJECTED = 'rejected';
|
||||
|
||||
const APPLICATION_PENDING = 'pending';
|
||||
const APPLICATION_APPROVED = 'approved';
|
||||
const APPLICATION_REJECTED = 'rejected';
|
||||
|
||||
const STICKY_NONE = 0;
|
||||
const STICKY_PLATE = 1;
|
||||
const STICKY_SUPER = 2;
|
||||
const STICKY_GLOBAL = 3;
|
||||
|
||||
const BBS_TYPE_STANDARD = 'standard';
|
||||
const BBS_TYPE_QUESTION = 'question';
|
||||
|
||||
const QUESTION_UNSOLVED = 0;
|
||||
const QUESTION_SOLVED = 1;
|
||||
|
||||
public static function get_instance(): self {
|
||||
if (null === self::$instance) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
private function permission_service(): AKSRT_Forum_Permission_Service {
|
||||
if (!$this->perm_service) {
|
||||
$this->perm_service = new AKSRT_Forum_Permission_Service($this);
|
||||
}
|
||||
return $this->perm_service;
|
||||
}
|
||||
|
||||
private function score_service(): AKSRT_Forum_Score_Service {
|
||||
if (!$this->score_service) {
|
||||
$this->score_service = new AKSRT_Forum_Score_Service($this);
|
||||
}
|
||||
return $this->score_service;
|
||||
}
|
||||
|
||||
private function comment_service(): AKSRT_Forum_Comment_Service {
|
||||
if (!$this->comment_service) {
|
||||
$this->comment_service = new AKSRT_Forum_Comment_Service($this);
|
||||
}
|
||||
return $this->comment_service;
|
||||
}
|
||||
|
||||
private function draft_service(): AKSRT_Forum_Draft_Service {
|
||||
if (!$this->draft_svc) {
|
||||
// Draft service is loaded by bootstrap before this class.
|
||||
if (!class_exists('AKSRT_Forum_Draft_Service', false)) {
|
||||
require_once __DIR__ . '/class-aksrt-forum-draft-service.php';
|
||||
}
|
||||
$this->draft_svc = new AKSRT_Forum_Draft_Service($this);
|
||||
}
|
||||
return $this->draft_svc;
|
||||
}
|
||||
|
||||
private ?AKSRT_Forum_Topic_Service $topic_svc = null;
|
||||
|
||||
private ?AKSRT_Forum_Moderation_Service $mod_svc = null;
|
||||
private function moderation_service(): AKSRT_Forum_Moderation_Service {
|
||||
if ($this->mod_svc === null) {
|
||||
$this->mod_svc = new AKSRT_Forum_Moderation_Service($this);
|
||||
}
|
||||
return $this->mod_svc;
|
||||
}
|
||||
|
||||
private ?AKSRT_Forum_View_Builder $view_builder = null;
|
||||
private function view_builder(): AKSRT_Forum_View_Builder {
|
||||
if ($this->view_builder === null) {
|
||||
$this->view_builder = new AKSRT_Forum_View_Builder($this);
|
||||
}
|
||||
return $this->view_builder;
|
||||
}
|
||||
|
||||
private function topic_service(): AKSRT_Forum_Topic_Service {
|
||||
if ($this->topic_svc === null) {
|
||||
$this->topic_svc = new AKSRT_Forum_Topic_Service($this);
|
||||
}
|
||||
return $this->topic_svc;
|
||||
}
|
||||
private function board_service(): AKSRT_Forum_Board_Service {
|
||||
if (!$this->board_svc) {
|
||||
$this->board_svc = new AKSRT_Forum_Board_Service($this);
|
||||
}
|
||||
return $this->board_svc;
|
||||
}
|
||||
|
||||
public function is_plate_cpt_active(): bool {
|
||||
// Plate CPT is the native board model; no option migration gate.
|
||||
return post_type_exists('aksrt_forum_plate');
|
||||
}
|
||||
|
||||
public function get_config(): array {
|
||||
if (class_exists('AKSRT_Settings')) {
|
||||
return AKSRT_Settings::get_forum_settings();
|
||||
}
|
||||
|
||||
return ['enabled' => false];
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias kept for topic-service helpers that still call get_forum_config().
|
||||
*/
|
||||
public function get_forum_config(): array {
|
||||
return $this->get_config();
|
||||
}
|
||||
|
||||
public function get_boards(array $args = []): array {
|
||||
return $this->board_service()->get_boards($args);
|
||||
}
|
||||
|
||||
public function create_board(array $data): int {
|
||||
return $this->board_service()->create_board($data);
|
||||
}
|
||||
|
||||
public function update_board(int $board_id, array $data): bool {
|
||||
return $this->board_service()->update_board($board_id, $data);
|
||||
}
|
||||
|
||||
public function delete_board(int $board_id): bool {
|
||||
return $this->board_service()->delete_board($board_id);
|
||||
}
|
||||
|
||||
public function reorder_boards(array $order_map): bool {
|
||||
return $this->board_service()->reorder_boards($order_map);
|
||||
}
|
||||
|
||||
// §7.0 Plate CPT methods —板块 CPT 化 CRUD
|
||||
|
||||
public function get_plates(array $args = []): array {
|
||||
return $this->board_service()->get_plates($args);
|
||||
}
|
||||
|
||||
public function get_plate(int $plate_id): ?array {
|
||||
return $this->board_service()->get_plate($plate_id);
|
||||
}
|
||||
|
||||
public function build_plate_payload(\WP_Post $post): array {
|
||||
return $this->board_service()->build_plate_payload($post);
|
||||
}
|
||||
|
||||
public function create_plate(array $data): int {
|
||||
return $this->board_service()->create_plate($data);
|
||||
}
|
||||
|
||||
public function update_plate(int $plate_id, array $data): bool {
|
||||
return $this->board_service()->update_plate($plate_id, $data);
|
||||
}
|
||||
|
||||
public function delete_plate(int $plate_id, string $cascade = 'trash', int $move_to = 0): bool {
|
||||
return $this->board_service()->delete_plate($plate_id, $cascade, $move_to);
|
||||
}
|
||||
|
||||
public function reorder_plates(array $order_map): bool {
|
||||
return $this->board_service()->reorder_plates($order_map);
|
||||
}
|
||||
|
||||
public function get_topic_plate_id(int $topic_id): int {
|
||||
return $this->board_service()->get_topic_plate_id($topic_id);
|
||||
}
|
||||
|
||||
public function is_plate_moderator(int $user_id, int $plate_id): bool {
|
||||
return $this->board_service()->is_plate_moderator($user_id, $plate_id);
|
||||
}
|
||||
|
||||
public function get_plate_view(int $plate_id, array $args = []): array {
|
||||
return $this->board_service()->get_plate_view($plate_id, $args);
|
||||
}
|
||||
|
||||
public function get_topics(array $args = []): array {
|
||||
$view = $this->get_topic_list($args);
|
||||
return $view['items'];
|
||||
}
|
||||
|
||||
public function get_topic_list(array $args = []): array {
|
||||
return $this->view_builder()->get_topic_list($args);
|
||||
}
|
||||
|
||||
public function get_home_view(int $user_id = 0): array {
|
||||
return $this->view_builder()->get_home_view($user_id);
|
||||
}
|
||||
|
||||
public function get_follow_stream(int $user_id, int $page = 1, int $limit = 15): array {
|
||||
return $this->view_builder()->get_follow_stream($user_id, $page, $limit);
|
||||
}
|
||||
|
||||
public function get_synthesis_stream(int $page = 1, int $limit = 15, int $viewer = 0): array {
|
||||
return $this->view_builder()->get_synthesis_stream($page, $limit, $viewer);
|
||||
}
|
||||
|
||||
public function get_pending_stream(int $user_id, int $page = 1, int $limit = 15): array {
|
||||
return $this->view_builder()->get_pending_stream($user_id, $page, $limit);
|
||||
}
|
||||
|
||||
public function ajax_home_tab(): void {
|
||||
$this->view_builder()->ajax_home_tab();
|
||||
}
|
||||
|
||||
public function ajax_board_topics(): void {
|
||||
$this->view_builder()->ajax_board_topics();
|
||||
}
|
||||
|
||||
public function render_board_cards(array $boards): void {
|
||||
$this->view_builder()->render_board_cards($boards);
|
||||
}
|
||||
|
||||
public function render_topic_row(array $topic): void {
|
||||
$this->view_builder()->render_topic_row($topic);
|
||||
}
|
||||
|
||||
public function get_board_view(int $board_id, array $args = []): array {
|
||||
return $this->board_service()->get_board_view($board_id, $args);
|
||||
}
|
||||
|
||||
public function can_view_plate(int $user_id, int $plate_id, string $allow_view = ''): bool {
|
||||
return $this->board_service()->can_view_plate($user_id, $plate_id, $allow_view);
|
||||
}
|
||||
|
||||
public function can_compose_in_plate(int $user_id, int $plate_id): bool {
|
||||
return $this->board_service()->can_compose_in_plate($user_id, $plate_id);
|
||||
}
|
||||
|
||||
public function is_user_following_plate(int $user_id, int $plate_id): bool {
|
||||
return $this->board_service()->is_user_following_plate($user_id, $plate_id);
|
||||
}
|
||||
|
||||
public function is_plate_follow_pay_enabled(int $plate_id): bool {
|
||||
return $this->board_service()->is_plate_follow_pay_enabled($plate_id);
|
||||
}
|
||||
|
||||
public function resolve_plate_follow_pay_price(int $plate_id, int $user_id = 0): array {
|
||||
return $this->board_service()->resolve_plate_follow_pay_price($plate_id, $user_id);
|
||||
}
|
||||
|
||||
public function mark_plate_followed(int $user_id, int $plate_id): void {
|
||||
$this->board_service()->mark_plate_followed($user_id, $plate_id);
|
||||
}
|
||||
|
||||
public function toggle_follow_plate(int $user_id, int $plate_id): array {
|
||||
return $this->board_service()->toggle_follow_plate($user_id, $plate_id);
|
||||
}
|
||||
|
||||
public function get_user_followed_plates(int $user_id): array {
|
||||
return $this->board_service()->get_user_followed_plates($user_id);
|
||||
}
|
||||
|
||||
public function get_user_managed_plates(int $user_id): array {
|
||||
return $this->board_service()->get_user_managed_plates($user_id);
|
||||
}
|
||||
|
||||
public function get_user_forum_topics(int $user_id, array $args = []): array {
|
||||
return $this->view_builder()->get_user_forum_topics($user_id, $args);
|
||||
}
|
||||
|
||||
public function get_user_favorite_topics(int $user_id, int $page = 1, int $limit = 10): array {
|
||||
return $this->view_builder()->get_user_favorite_topics($user_id, $page, $limit);
|
||||
}
|
||||
|
||||
public function get_user_forum_stats(int $user_id): array {
|
||||
return $this->view_builder()->get_user_forum_stats($user_id);
|
||||
}
|
||||
|
||||
public function increment_plate_views(int $plate_id): void {
|
||||
$this->board_service()->increment_plate_views($plate_id);
|
||||
}
|
||||
|
||||
public function is_hot_plate(int $plate_id): bool {
|
||||
return $this->board_service()->is_hot_plate($plate_id);
|
||||
}
|
||||
|
||||
public function get_plate_categories(): array {
|
||||
return $this->board_service()->get_plate_categories();
|
||||
}
|
||||
|
||||
public function is_cat_moderator(int $user_id, int $cat_id): bool {
|
||||
return $this->board_service()->is_cat_moderator($user_id, $cat_id);
|
||||
}
|
||||
|
||||
public function get_sorted_plates(string $orderby = 'menu_order', string $order = 'ASC', int $limit = 50): array {
|
||||
return $this->board_service()->get_sorted_plates($orderby, $order, $limit);
|
||||
}
|
||||
|
||||
public function get_topic_view(int $topic_id, int $user_id = 0): array {
|
||||
return $this->view_builder()->get_topic_view($topic_id, $user_id);
|
||||
}
|
||||
|
||||
public function get_compose_view(int $user_id, array $args = []): array {
|
||||
return $this->view_builder()->get_compose_view($user_id, $args);
|
||||
}
|
||||
|
||||
public function get_stats(): array {
|
||||
return $this->view_builder()->get_stats();
|
||||
}
|
||||
|
||||
public function can_edit_topic(int $user_id, int $topic_id): bool {
|
||||
return $this->topic_service()->can_edit_topic($user_id, $topic_id);
|
||||
}
|
||||
|
||||
public function can_moderate_topic(int $user_id, int $topic_id): bool {
|
||||
return $this->forum_user_can($user_id, 'topic_moderate', $topic_id);
|
||||
}
|
||||
|
||||
public function update_topic(int $topic_id, array $data) {
|
||||
return $this->topic_service()->update_topic($topic_id, $data);
|
||||
}
|
||||
|
||||
public function get_tags(): array {
|
||||
return $this->view_builder()->get_tags();
|
||||
}
|
||||
|
||||
public function get_tag_archive_view(array $args = []): array {
|
||||
return $this->view_builder()->get_tag_archive_view($args);
|
||||
}
|
||||
|
||||
public function init(): void {
|
||||
add_action('wp_ajax_aksrt_forum_update_topic', [$this, 'ajax_update_topic']);
|
||||
add_action('wp_ajax_aksrt_forum_topic_list', [$this, 'ajax_topic_list']);
|
||||
add_action('wp_ajax_aksrt_forum_create_topic', [$this, 'ajax_create_topic']);
|
||||
add_action('wp_ajax_aksrt_forum_follow_plate', [$this, 'ajax_follow_plate']);
|
||||
add_action('wp_ajax_aksrt_forum_purchase_plate_follow', [$this, 'ajax_purchase_plate_follow']);
|
||||
add_action('wp_ajax_aksrt_forum_unlock_topic', [$this, 'ajax_unlock_topic']);
|
||||
|
||||
// §7.4 draft persistence
|
||||
add_action('wp_ajax_aksrt_forum_save_draft', [$this, 'ajax_save_draft']);
|
||||
add_action('wp_ajax_aksrt_forum_load_draft', [$this, 'ajax_load_draft']);
|
||||
add_action('wp_ajax_aksrt_forum_delete_draft', [$this, 'ajax_delete_draft']);
|
||||
|
||||
// §7.4 remote search selectors
|
||||
add_action('wp_ajax_aksrt_forum_search_boards', [$this, 'ajax_search_boards']);
|
||||
add_action('wp_ajax_nopriv_aksrt_forum_search_boards', [$this, 'ajax_search_boards']);
|
||||
add_action('wp_ajax_aksrt_forum_search_tags', [$this, 'ajax_search_tags']);
|
||||
add_action('wp_ajax_nopriv_aksrt_forum_search_tags', [$this, 'ajax_search_tags']);
|
||||
add_action('wp_ajax_aksrt_forum_search_topics', [$this, 'ajax_search_topics']);
|
||||
add_action('wp_ajax_nopriv_aksrt_forum_search_topics', [$this, 'ajax_search_topics']);
|
||||
|
||||
// §7.4 AJAX-based topic editing
|
||||
add_action('wp_ajax_aksrt_forum_edit_topic', [$this, 'ajax_edit_topic']);
|
||||
|
||||
// §7.6 forum comment engine
|
||||
add_action('wp_ajax_aksrt_forum_comments', [$this, 'ajax_forum_comments']);
|
||||
add_action('wp_ajax_nopriv_aksrt_forum_comments', [$this, 'ajax_forum_comments']);
|
||||
add_action('wp_ajax_aksrt_forum_comment_top', [$this, 'ajax_comment_topping']);
|
||||
|
||||
self::register_hide_shortcode();
|
||||
|
||||
// §7.10 forum home tab AJAX
|
||||
add_action('wp_ajax_aksrt_forum_home_tab', [$this, 'ajax_home_tab']);
|
||||
add_action('wp_ajax_nopriv_aksrt_forum_home_tab', [$this, 'ajax_home_tab']);
|
||||
|
||||
// Public plate/board topic list (sort tabs)
|
||||
add_action('wp_ajax_aksrt_forum_board_topics', [$this, 'ajax_board_topics']);
|
||||
add_action('wp_ajax_nopriv_aksrt_forum_board_topics', [$this, 'ajax_board_topics']);
|
||||
|
||||
add_action('admin_init', [$this, 'maybe_migrate_boards'], 25);
|
||||
add_filter('template_include', [$this, 'template_routing']);
|
||||
add_action('wp', [$this, 'maybe_refresh_recommend_on_view']);
|
||||
add_action('comment_post', [$this, 'on_forum_comment_posted'], 10, 3);
|
||||
|
||||
// §7.2 板块多维统计 hooks
|
||||
add_action('save_post_aksrt_forum_topic', [$this, 'on_topic_saved_sync_plate'], 20, 2);
|
||||
add_action('comment_post', [$this, 'on_comment_sync_plate_reply'], 25, 3);
|
||||
|
||||
add_action('wp_login', [$this, 'cap_cache_on_login'], 10, 2);
|
||||
add_action('updated_post_meta', [$this, 'cap_cache_on_plate_meta'], 10, 3);
|
||||
add_action('added_post_meta', [$this, 'cap_cache_on_plate_meta'], 10, 3);
|
||||
add_action('deleted_post_meta', [$this, 'cap_cache_on_plate_meta'], 10, 3);
|
||||
add_action('updated_term_meta', [$this, 'cap_cache_on_cat_meta'], 10, 3);
|
||||
add_action('save_post_aksrt_forum_plate', [$this, 'cap_cache_on_plate_save'], 10, 1);
|
||||
}
|
||||
|
||||
public function on_topic_saved_sync_plate(int $post_id, \WP_Post $post): void {
|
||||
$this->moderation_service()->on_topic_saved_sync_plate($post_id, $post);
|
||||
}
|
||||
|
||||
public function on_comment_sync_plate_reply(int $comment_id, $approved, array $commentdata): void {
|
||||
$this->moderation_service()->on_comment_sync_plate_reply($comment_id, $approved, $commentdata);
|
||||
}
|
||||
|
||||
public function template_routing(string $template): string {
|
||||
return $this->view_builder()->template_routing($template);
|
||||
}
|
||||
|
||||
public function update_topic_meta(int $topic_id, array $meta): bool {
|
||||
return $this->topic_service()->update_topic_meta($topic_id, $meta);
|
||||
}
|
||||
|
||||
public function is_moderator(int $user_id, int $topic_id): bool {
|
||||
return $this->moderation_service()->is_moderator($user_id, $topic_id);
|
||||
}
|
||||
|
||||
public function ajax_update_topic(): void {
|
||||
$this->topic_service()->ajax_update_topic();
|
||||
}
|
||||
|
||||
public function ajax_topic_list(): void {
|
||||
$this->view_builder()->ajax_topic_list();
|
||||
}
|
||||
|
||||
public function ajax_create_topic(): void {
|
||||
$this->topic_service()->ajax_create_topic();
|
||||
}
|
||||
|
||||
public function create_topic(int $user_id, array $data) {
|
||||
return $this->topic_service()->create_topic($user_id, $data);
|
||||
}
|
||||
|
||||
public function moderate_topic(int $topic_id, string $action, array $payload, int $operator_id = 0) {
|
||||
return $this->topic_service()->moderate_topic($topic_id, $action, $payload, $operator_id);
|
||||
}
|
||||
|
||||
public function submit_vote(int $topic_id, int $user_id, array $option_ids) {
|
||||
return $this->topic_service()->submit_vote($topic_id, $user_id, $option_ids);
|
||||
}
|
||||
|
||||
public function ajax_follow_plate(): void {
|
||||
$this->board_service()->ajax_follow_plate();
|
||||
}
|
||||
|
||||
public function ajax_purchase_plate_follow(): void {
|
||||
$this->board_service()->ajax_purchase_plate_follow();
|
||||
}
|
||||
|
||||
public function ajax_unlock_topic(): void {
|
||||
$this->topic_service()->ajax_unlock_topic();
|
||||
}
|
||||
|
||||
/**
|
||||
* Lightweight result helper for topic service actions.
|
||||
*
|
||||
* @return array{success:bool,message:string}
|
||||
*/
|
||||
public function success(string $message = '', array $extra = []): array {
|
||||
return array_merge(['success' => true, 'message' => $message], $extra);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{success:bool,message:string}
|
||||
*/
|
||||
public function error(string $message = '', array $extra = []): array {
|
||||
return array_merge(['success' => false, 'message' => $message], $extra);
|
||||
}
|
||||
|
||||
public function apply_moderator(int $user_id, int $board_id, string $reason) {
|
||||
return $this->moderation_service()->apply_moderator($user_id, $board_id, $reason);
|
||||
}
|
||||
|
||||
public function review_moderator_application(string $application_id, string $status, string $note, int $reviewer_id) {
|
||||
return $this->moderation_service()->review_moderator_application($application_id, $status, $note, $reviewer_id);
|
||||
}
|
||||
|
||||
public function get_moderator_application_list(array $args = []): array {
|
||||
return $this->moderation_service()->get_moderator_application_list($args);
|
||||
}
|
||||
|
||||
public function sync_board_count(int $board_id): void {
|
||||
$this->moderation_service()->sync_board_count($board_id);
|
||||
}
|
||||
|
||||
public function sync_plate_counts(int $plate_id): void {
|
||||
$this->moderation_service()->sync_plate_counts($plate_id);
|
||||
}
|
||||
|
||||
public function get_board_moderators(int $board_id): array {
|
||||
return $this->moderation_service()->get_board_moderators($board_id);
|
||||
}
|
||||
|
||||
public function get_plate_moderators(int $plate_id): array {
|
||||
return $this->moderation_service()->get_plate_moderators($plate_id);
|
||||
}
|
||||
|
||||
public function is_forum_admin(int $user_id): bool {
|
||||
return $this->moderation_service()->is_forum_admin($user_id);
|
||||
}
|
||||
|
||||
public function is_board_moderator(int $user_id, int $board_id): bool {
|
||||
return $this->moderation_service()->is_board_moderator($user_id, $board_id);
|
||||
}
|
||||
|
||||
public function get_board_moderator_profiles(int $board_id): array {
|
||||
return $this->moderation_service()->get_board_moderator_profiles($board_id);
|
||||
}
|
||||
|
||||
public function get_board_moderator_profiles_with_roles(int $board_id): array {
|
||||
return $this->moderation_service()->get_board_moderator_profiles_with_roles($board_id);
|
||||
}
|
||||
|
||||
public function get_moderator_roles(int $board_id): array {
|
||||
return $this->moderation_service()->get_moderator_roles($board_id);
|
||||
}
|
||||
|
||||
public function set_moderator_role(int $user_id, int $board_id, string $role): bool {
|
||||
return $this->moderation_service()->set_moderator_role($user_id, $board_id, $role);
|
||||
}
|
||||
|
||||
public function add_moderator(int $user_id, int $board_id, string $role = 'moderator'): bool {
|
||||
return $this->moderation_service()->add_moderator($user_id, $board_id, $role);
|
||||
}
|
||||
|
||||
public function remove_moderator(int $user_id, int $board_id): bool {
|
||||
return $this->moderation_service()->remove_moderator($user_id, $board_id);
|
||||
}
|
||||
|
||||
public function get_user_moderator_boards(int $user_id): array {
|
||||
return $this->moderation_service()->get_user_moderator_boards($user_id);
|
||||
}
|
||||
|
||||
public function has_moderator_role(int $user_id, int $board_id, string $min_role = 'moderator'): bool {
|
||||
return $this->moderation_service()->has_moderator_role($user_id, $board_id, $min_role);
|
||||
}
|
||||
|
||||
public function get_board_moderator_applications(int $board_id): array {
|
||||
return $this->moderation_service()->get_board_moderator_applications($board_id);
|
||||
}
|
||||
|
||||
public function get_plate_moderator_applications(int $plate_id): array {
|
||||
return $this->moderation_service()->get_plate_moderator_applications($plate_id);
|
||||
}
|
||||
|
||||
public function get_board_application_for_user(int $user_id, int $board_id): ?array {
|
||||
return $this->moderation_service()->get_board_application_for_user($user_id, $board_id);
|
||||
}
|
||||
|
||||
public function get_visibility_options(): array {
|
||||
return $this->topic_service()->get_visibility_options();
|
||||
}
|
||||
|
||||
public function get_review_options(): array {
|
||||
return $this->topic_service()->get_review_options();
|
||||
}
|
||||
|
||||
public function can_user_publish_directly(int $user_id, int $board_id = 0): bool {
|
||||
return $this->topic_service()->can_user_publish_directly($user_id, $board_id);
|
||||
}
|
||||
|
||||
public function get_sticky_topics_for_board(int $board_id): array {
|
||||
return $this->view_builder()->get_sticky_topics_for_board($board_id);
|
||||
}
|
||||
|
||||
public function get_topic_detail(int $topic_id, int $viewer_user_id = 0): array {
|
||||
return $this->view_builder()->get_topic_detail($topic_id, $viewer_user_id);
|
||||
}
|
||||
|
||||
public function increment_view_count(int $topic_id): void {
|
||||
$this->view_builder()->increment_view_count($topic_id);
|
||||
}
|
||||
|
||||
public function maybe_migrate_boards(): void {
|
||||
$this->board_service()->maybe_migrate_boards();
|
||||
}
|
||||
|
||||
public function build_topic_payload(WP_Post $post, int $viewer_user_id = 0): array {
|
||||
return $this->topic_service()->build_topic_payload($post, $viewer_user_id);
|
||||
}
|
||||
|
||||
public function unlock_topic_with_credits(int $user_id, int $topic_id): array {
|
||||
return $this->topic_service()->unlock_topic_with_credits($user_id, $topic_id);
|
||||
}
|
||||
|
||||
public function has_user_unlocked_topic(int $user_id, int $topic_id): bool {
|
||||
return $this->topic_service()->has_user_unlocked_topic($user_id, $topic_id);
|
||||
}
|
||||
|
||||
public function has_user_paid_topic(int $user_id, int $topic_id): bool {
|
||||
return $this->topic_service()->has_user_paid_topic($user_id, $topic_id);
|
||||
}
|
||||
|
||||
public function resolve_topic_pay_price(int $topic_id, int $user_id = 0): array {
|
||||
return $this->topic_service()->resolve_topic_pay_price($topic_id, $user_id);
|
||||
}
|
||||
|
||||
public function mark_topic_unlocked(int $user_id, int $topic_id): void {
|
||||
$this->topic_service()->mark_topic_unlocked($user_id, $topic_id);
|
||||
}
|
||||
|
||||
public function prepare_vote_meta(array $data): array {
|
||||
return $this->topic_service()->prepare_vote_meta($data);
|
||||
}
|
||||
|
||||
public function save_vote_meta(int $topic_id, array $vote_meta): void {
|
||||
$this->topic_service()->save_vote_meta($topic_id, $vote_meta);
|
||||
}
|
||||
|
||||
public function normalize_post_status_query(string $status) {
|
||||
return $this->topic_service()->normalize_post_status_query($status);
|
||||
}
|
||||
|
||||
private function build_user_summary(int $user_id): array {
|
||||
return $this->topic_service()->build_user_summary($user_id);
|
||||
}
|
||||
|
||||
private function get_user_avatar_url(int $user_id): string {
|
||||
return $this->topic_service()->get_user_avatar_url($user_id);
|
||||
}
|
||||
|
||||
public function save_cover_meta(int $post_id, array $data): void {
|
||||
$this->topic_service()->save_cover_meta($post_id, $data);
|
||||
}
|
||||
|
||||
public function split_tags(string $tags): array {
|
||||
return $this->topic_service()->split_tags($tags);
|
||||
}
|
||||
|
||||
private function get_visibility_map(): array {
|
||||
return $this->topic_service()->get_visibility_map();
|
||||
}
|
||||
|
||||
private function get_review_map(): array {
|
||||
return $this->topic_service()->get_review_map();
|
||||
}
|
||||
|
||||
private function get_visibility_label(string $visibility): string {
|
||||
return $this->topic_service()->get_visibility_label($visibility);
|
||||
}
|
||||
|
||||
private function get_review_label(string $review_status): string {
|
||||
return $this->topic_service()->get_review_label($review_status);
|
||||
}
|
||||
|
||||
public function is_favorited(int $user_id, int $topic_id): bool {
|
||||
return $this->score_service()->is_favorited($user_id, $topic_id);
|
||||
}
|
||||
|
||||
public function toggle_favorite(int $user_id, int $topic_id) {
|
||||
return $this->score_service()->toggle_favorite($user_id, $topic_id);
|
||||
}
|
||||
|
||||
public function score_topic(int $user_id, int $topic_id, int $amount) {
|
||||
return $this->score_service()->score_topic($user_id, $topic_id, $amount);
|
||||
}
|
||||
|
||||
public function get_score_detail(int $topic_id): array {
|
||||
return $this->score_service()->get_score_detail($topic_id);
|
||||
}
|
||||
|
||||
public function calculate_recommend_score(int $topic_id): float {
|
||||
return $this->score_service()->calculate_recommend_score($topic_id);
|
||||
}
|
||||
|
||||
public function refresh_recommend_score(int $topic_id): float {
|
||||
return $this->score_service()->refresh_recommend_score($topic_id);
|
||||
}
|
||||
|
||||
public function batch_refresh_recommend_scores(int $limit = 100): int {
|
||||
return $this->score_service()->batch_refresh_recommend_scores($limit);
|
||||
}
|
||||
|
||||
public function maybe_refresh_recommend_on_view(): void {
|
||||
$this->score_service()->maybe_refresh_recommend_on_view();
|
||||
}
|
||||
|
||||
public function notify(int $user_id, string $type, string $content, array $data = []): void {
|
||||
$this->score_service()->notify($user_id, $type, $content, $data);
|
||||
}
|
||||
|
||||
public function notify_topic_pending(int $topic_id, int $author_id): void {
|
||||
$this->score_service()->notify_topic_pending($topic_id, $author_id);
|
||||
}
|
||||
|
||||
public function notify_moderation_result(int $topic_id, string $action, int $operator_id): void {
|
||||
$this->score_service()->notify_moderation_result($topic_id, $action, $operator_id);
|
||||
}
|
||||
|
||||
public function notify_topic_reply(int $topic_id, int $commenter_id): void {
|
||||
$this->score_service()->notify_topic_reply($topic_id, $commenter_id);
|
||||
}
|
||||
|
||||
public function notify_topic_scored(int $topic_id, int $scorer_id, int $amount): void {
|
||||
$this->score_service()->notify_topic_scored($topic_id, $scorer_id, $amount);
|
||||
}
|
||||
|
||||
const COMMENT_TOP_NONE = 0;
|
||||
const COMMENT_TOP_NORMAL = 1;
|
||||
const COMMENT_TOP_HOT = 2;
|
||||
const COMMENT_TOP_BEST = 3;
|
||||
|
||||
public function on_forum_comment_posted(int $comment_id, $approved, array $commentdata = []): void {
|
||||
$this->score_service()->on_forum_comment_posted($comment_id, $approved, $commentdata);
|
||||
}
|
||||
|
||||
public function ajax_forum_comments(): void {
|
||||
$this->comment_service()->ajax_forum_comments();
|
||||
}
|
||||
|
||||
public function ajax_comment_topping(): void {
|
||||
$this->comment_service()->ajax_comment_topping();
|
||||
}
|
||||
|
||||
public function render_forum_comments(array $comments, int $topic_id, int $adopted_id = 0): void {
|
||||
$this->comment_service()->render_forum_comments($comments, $topic_id, $adopted_id);
|
||||
}
|
||||
|
||||
public function adopt_answer(int $topic_id, int $comment_id, int $operator_id) {
|
||||
return $this->comment_service()->adopt_answer($topic_id, $comment_id, $operator_id);
|
||||
}
|
||||
|
||||
public function get_adopted_comment_id(int $topic_id): int {
|
||||
return $this->comment_service()->get_adopted_comment_id($topic_id);
|
||||
}
|
||||
|
||||
public function get_forum_comments(int $topic_id, string $sort = 'newest', bool $author_only = false, int $page = 1, int $per_page = 30): array {
|
||||
return $this->comment_service()->get_forum_comments($topic_id, $sort, $author_only, $page, $per_page);
|
||||
}
|
||||
|
||||
public function set_comment_topping(int $comment_id, int $level): bool {
|
||||
return $this->comment_service()->set_comment_topping($comment_id, $level);
|
||||
}
|
||||
|
||||
public function maybe_mark_hot_comment(int $comment_id): bool {
|
||||
return $this->comment_service()->maybe_mark_hot_comment($comment_id);
|
||||
}
|
||||
|
||||
public function is_hot_comment(int $comment_id): bool {
|
||||
return $this->comment_service()->is_hot_comment($comment_id);
|
||||
}
|
||||
|
||||
public function get_commenter_badge(int $user_id, int $topic_id): ?array {
|
||||
return $this->comment_service()->get_commenter_badge($user_id, $topic_id);
|
||||
}
|
||||
|
||||
public function save_draft(int $user_id, array $data) {
|
||||
return $this->draft_service()->save_draft($user_id, $data);
|
||||
}
|
||||
|
||||
public function get_user_draft_count(int $user_id): int {
|
||||
return $this->draft_service()->get_user_draft_count($user_id);
|
||||
}
|
||||
|
||||
public function get_user_drafts(int $user_id, int $limit = 20): array {
|
||||
return $this->draft_service()->get_user_drafts($user_id, $limit);
|
||||
}
|
||||
|
||||
public function delete_draft(int $user_id, int $draft_id): bool {
|
||||
return $this->draft_service()->delete_draft($user_id, $draft_id);
|
||||
}
|
||||
|
||||
public function load_draft_data(int $user_id, int $draft_id): ?array {
|
||||
return $this->draft_service()->load_draft_data($user_id, $draft_id);
|
||||
}
|
||||
|
||||
public function ajax_save_draft(): void {
|
||||
$this->draft_service()->ajax_save_draft();
|
||||
}
|
||||
|
||||
public function ajax_load_draft(): void {
|
||||
$this->draft_service()->ajax_load_draft();
|
||||
}
|
||||
|
||||
public function ajax_delete_draft(): void {
|
||||
$this->draft_service()->ajax_delete_draft();
|
||||
}
|
||||
|
||||
public function ajax_search_boards(): void {
|
||||
$this->view_builder()->ajax_search_boards();
|
||||
}
|
||||
|
||||
public function ajax_search_tags(): void {
|
||||
$this->view_builder()->ajax_search_tags();
|
||||
}
|
||||
|
||||
public function ajax_search_topics(): void {
|
||||
$this->view_builder()->ajax_search_topics();
|
||||
}
|
||||
|
||||
public function ajax_edit_topic(): void {
|
||||
$this->topic_service()->ajax_edit_topic();
|
||||
}
|
||||
|
||||
public function get_compose_capabilities(int $user_id): array {
|
||||
return $this->permission_service()->get_compose_capabilities($user_id);
|
||||
}
|
||||
|
||||
public function sanitize_topic_content(int $user_id, string $content): string {
|
||||
return $this->topic_service()->sanitize_topic_content($user_id, $content);
|
||||
}
|
||||
|
||||
public static function register_hide_shortcode(): void {
|
||||
add_shortcode('aksrt_hide', [AKSRT_Forum_Topic_Service::class, 'render_hide_shortcode']);
|
||||
}
|
||||
|
||||
public function publish_draft(int $user_id, int $draft_id, array $data) {
|
||||
return $this->draft_service()->publish_draft($user_id, $draft_id, $data);
|
||||
}
|
||||
|
||||
//
|
||||
// 四级层级(对标 zibll):
|
||||
// TIER_FORUM_ADMIN (4) — bbs_admin_users + manage_options
|
||||
// TIER_CAT_MOD (3) — aksrt_plate_cat term meta 'moderator'
|
||||
// TIER_PLATE_AUTHOR(2) — plate CPT post_author(超级版主)
|
||||
// TIER_MODERATOR (1) — plate CPT post meta '_aksrt_moderators' list
|
||||
//
|
||||
// 公开 API:
|
||||
// forum_user_can( $uid, $cap, $context_id = 0 ): bool
|
||||
// get_user_plate_tier( $uid, $plate_id ): int
|
||||
// flush_cap_cache( $uid = 0 ): void
|
||||
|
||||
public function get_user_plate_tier(int $user_id, int $plate_id): int {
|
||||
return $this->permission_service()->get_user_plate_tier($user_id, $plate_id);
|
||||
}
|
||||
|
||||
public function forum_user_can(int $user_id, string $cap, int $context_id = 0): bool {
|
||||
return $this->permission_service()->forum_user_can($user_id, $cap, $context_id);
|
||||
}
|
||||
|
||||
public function flush_cap_cache(int $user_id = 0): void {
|
||||
$this->permission_service()->flush_cap_cache($user_id);
|
||||
}
|
||||
|
||||
public function cap_cache_on_login(string $user_login, \WP_User $user): void {
|
||||
$this->permission_service()->cap_cache_on_login($user_login, $user);
|
||||
}
|
||||
|
||||
public function cap_cache_on_plate_meta(int $meta_id, int $object_id, string $meta_key): void {
|
||||
$this->permission_service()->cap_cache_on_plate_meta($meta_id, $object_id, $meta_key);
|
||||
}
|
||||
|
||||
public function cap_cache_on_cat_meta(int $meta_id, int $object_id, string $meta_key): void {
|
||||
$this->permission_service()->cap_cache_on_cat_meta($meta_id, $object_id, $meta_key);
|
||||
}
|
||||
|
||||
public function cap_cache_on_plate_save(int $post_id): void {
|
||||
$this->permission_service()->cap_cache_on_plate_save($post_id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,871 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT Plate Follow Pay — multi-gateway cashier for paid plate follow (cash only).
|
||||
*
|
||||
* Contract:
|
||||
* - modo = money plates only (points stay on purchase_plate_follow AJAX)
|
||||
* - product_type = plate_follow, product_id = plate_id
|
||||
* - order note.plate_follow_pay = 1
|
||||
* - paid → mark_plate_followed (idempotent)
|
||||
* - optional coupons when _aksrt_follow_pay_coupon_s=1 (cash only)
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class AKSRT_Plate_Follow_Pay {
|
||||
|
||||
private static $instance = null;
|
||||
|
||||
public static function get_instance(): self {
|
||||
if (null === self::$instance) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
public function init(): void {
|
||||
add_action('rest_api_init', [$this, 'register_routes']);
|
||||
add_action('aksrt_order_paid', [$this, 'on_order_paid'], 16, 2);
|
||||
}
|
||||
|
||||
public function register_routes(): void {
|
||||
register_rest_route('aksrt/v1', '/pay/plate-follow/(?P<id>\\d+)', [
|
||||
[
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'rest_preview'],
|
||||
'permission_callback' => [$this, 'check_rest_nonce'],
|
||||
],
|
||||
[
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'rest_purchase'],
|
||||
'permission_callback' => [$this, 'check_rest_nonce'],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true|WP_Error
|
||||
*/
|
||||
public function check_rest_nonce(WP_REST_Request $request) {
|
||||
$nonce = $request->get_header('x-wp-nonce');
|
||||
if (!$nonce) {
|
||||
return new WP_Error('aksrt_rest_no_nonce', __('缺少安全令牌', 'aksrt'), ['status' => 403]);
|
||||
}
|
||||
if (!wp_verify_nonce($nonce, 'wp_rest')) {
|
||||
return new WP_Error('aksrt_rest_bad_nonce', __('安全令牌已过期,请刷新页面', 'aksrt'), ['status' => 403]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rest_preview(WP_REST_Request $request) {
|
||||
$plate_id = (int) $request['id'];
|
||||
$uid = get_current_user_id();
|
||||
$coupon_code = sanitize_text_field((string) ($request->get_param('coupon_code') ?? $request->get_param('coupon') ?? ''));
|
||||
$preview = $this->preview($plate_id, $uid, $coupon_code);
|
||||
if (is_wp_error($preview)) {
|
||||
return $preview;
|
||||
}
|
||||
return new WP_REST_Response($preview, 200);
|
||||
}
|
||||
|
||||
public function rest_purchase(WP_REST_Request $request) {
|
||||
$plate_id = (int) $request['id'];
|
||||
$uid = get_current_user_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('/')),
|
||||
]);
|
||||
}
|
||||
|
||||
$params = $request->get_json_params();
|
||||
if (!is_array($params)) {
|
||||
$params = $request->get_params();
|
||||
}
|
||||
|
||||
$payment_method = sanitize_key((string) ($params['payment_method'] ?? 'balance'));
|
||||
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(
|
||||
$this->create_cash_order($plate_id, $uid, $payment_method, $coupon_code)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $result
|
||||
* @return WP_REST_Response|WP_Error
|
||||
*/
|
||||
private function rest_wrap_result($result) {
|
||||
if (is_wp_error($result)) {
|
||||
return $result;
|
||||
}
|
||||
if (empty($result['success'])) {
|
||||
$code = sanitize_key((string) ($result['code'] ?? 'aksrt_pay_failed'));
|
||||
$status = (int) ($result['status'] ?? 400);
|
||||
return new WP_Error(
|
||||
$code !== '' ? $code : 'aksrt_pay_failed',
|
||||
(string) ($result['message'] ?? __('支付失败', 'aksrt')),
|
||||
array_merge(
|
||||
['status' => $status > 0 ? $status : 400],
|
||||
is_array($result['data'] ?? null) ? $result['data'] : []
|
||||
)
|
||||
);
|
||||
}
|
||||
return new WP_REST_Response($result, 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public function preview(int $plate_id, int $user_id = 0, string $coupon_code = '') {
|
||||
$assert = $this->assert_plate($plate_id);
|
||||
if (is_wp_error($assert)) {
|
||||
return $assert;
|
||||
}
|
||||
/** @var WP_Post $post */
|
||||
$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 && !$allow_guest) {
|
||||
return [
|
||||
'post_id' => $plate_id,
|
||||
'title' => get_the_title($post),
|
||||
'permalink' => get_permalink($post),
|
||||
'pay_modo' => '0',
|
||||
'pay_type' => 'plate_follow',
|
||||
'pay_type_label' => __('付费关注', 'aksrt'),
|
||||
'amount' => 0,
|
||||
'base' => 0,
|
||||
'list_amount' => 0,
|
||||
'payable' => 0,
|
||||
'currency' => '¥',
|
||||
'amount_label' => '',
|
||||
'base_label' => '',
|
||||
'list_amount_label' => '',
|
||||
'is_free' => false,
|
||||
'has_paid_access' => false,
|
||||
'need_login' => true,
|
||||
'allow_guest' => false,
|
||||
'need_vip' => false,
|
||||
'need_purchase' => true,
|
||||
'logged_in' => false,
|
||||
'balance' => 0,
|
||||
'balance_enough' => false,
|
||||
'payment_methods' => [],
|
||||
'login_url' => wp_login_url(get_permalink($post)),
|
||||
'vip_url' => home_url('/'),
|
||||
'credits_url' => home_url('/'),
|
||||
'coupon_allowed' => false,
|
||||
'coupon_desc' => '',
|
||||
'coupon_code' => '',
|
||||
'coupon' => null,
|
||||
'coupon_error' => '',
|
||||
'coupon_discount' => 0,
|
||||
'coupon_discount_label' => '',
|
||||
'rebate_discount' => 0,
|
||||
'rebate_discount_label' => '',
|
||||
'points_deduction_allowed' => false,
|
||||
'card_pass_allowed' => false,
|
||||
'price_type' => '',
|
||||
];
|
||||
}
|
||||
|
||||
$priced = $this->resolve_price($plate_id, $uid);
|
||||
if (($priced['modo'] ?? 'money') === 'points') {
|
||||
return new WP_Error('aksrt_wrong_modo', __('该版块仅支持积分关注。', 'aksrt'), ['status' => 400]);
|
||||
}
|
||||
|
||||
$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 ($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($uid > 0)
|
||||
: ($uid > 0
|
||||
? [['id' => 'balance', 'name' => __('站内余额', 'aksrt')]]
|
||||
: [['id' => 'alipay', 'name' => __('支付宝', 'aksrt')]]);
|
||||
|
||||
$coupon_allowed = $this->is_coupon_allowed($plate_id);
|
||||
$coupon_error = '';
|
||||
$coupon_discount = 0.0;
|
||||
$coupon_meta = null;
|
||||
$applied_code = '';
|
||||
$payable = $list_amount;
|
||||
|
||||
if (!$vip_free && $list_amount > 0) {
|
||||
$discounted = $this->apply_plate_cash_discounts($priced, $plate_id, $uid, $coupon_code, true);
|
||||
if (is_wp_error($discounted)) {
|
||||
$coupon_error = $discounted->get_error_message();
|
||||
$discounted = $this->apply_plate_cash_discounts($priced, $plate_id, $uid, '', true);
|
||||
}
|
||||
if (!is_wp_error($discounted)) {
|
||||
$list_amount = round((float) ($discounted['list_amount'] ?? $list_amount), 2);
|
||||
$payable = round((float) ($discounted['payable'] ?? $list_amount), 2);
|
||||
$coupon_discount = round((float) ($discounted['coupon_discount'] ?? 0), 2);
|
||||
$coupon_meta = !empty($discounted['coupon']) ? $discounted['coupon'] : null;
|
||||
$applied_code = (string) ($discounted['coupon_code'] ?? ($coupon_meta['code'] ?? ''));
|
||||
if ($coupon_error === '' && !empty($discounted['coupon_error'])) {
|
||||
$coupon_error = (string) $discounted['coupon_error'];
|
||||
}
|
||||
}
|
||||
} elseif ($vip_free) {
|
||||
$payable = 0.0;
|
||||
$list_amount = 0.0;
|
||||
}
|
||||
|
||||
$is_free = $vip_free || $payable <= 0;
|
||||
$amount_label = $is_free
|
||||
? __('免费', 'aksrt')
|
||||
: sprintf('¥%s', number_format($payable, 2, '.', ''));
|
||||
$list_label = sprintf('¥%s', number_format($list_amount > 0 ? $list_amount : $base, 2, '.', ''));
|
||||
$base_label = sprintf('¥%s', number_format($base, 2, '.', ''));
|
||||
|
||||
return [
|
||||
'post_id' => $plate_id,
|
||||
'title' => get_the_title($post),
|
||||
'permalink' => get_permalink($post),
|
||||
'pay_modo' => '0',
|
||||
'pay_type' => 'plate_follow',
|
||||
'pay_type_label' => __('付费关注', 'aksrt'),
|
||||
'amount' => $payable,
|
||||
'base' => $base,
|
||||
'list_amount' => $list_amount > 0 ? $list_amount : $base,
|
||||
'list_amount_label' => $list_label,
|
||||
'payable' => $payable,
|
||||
'currency' => '¥',
|
||||
'amount_label' => $amount_label,
|
||||
'base_label' => $base_label,
|
||||
'is_free' => $is_free,
|
||||
'has_paid_access' => $has_followed,
|
||||
'paid_order' => null,
|
||||
'pending_order' => null,
|
||||
'need_login' => $uid <= 0 && !$allow_guest,
|
||||
'allow_guest' => $allow_guest,
|
||||
'need_vip' => false,
|
||||
'need_purchase' => !$has_followed && !$is_free,
|
||||
'logged_in' => $uid > 0,
|
||||
'balance' => $balance,
|
||||
'balance_enough' => true,
|
||||
'payment_methods' => $methods,
|
||||
'login_url' => wp_login_url(get_permalink($post)),
|
||||
'vip_url' => home_url('/'),
|
||||
'credits_url' => home_url('/'),
|
||||
'coupon_allowed' => $coupon_allowed,
|
||||
'coupon_desc' => $coupon_allowed ? __('支持优惠码', 'aksrt') : '',
|
||||
'coupon_code' => $applied_code,
|
||||
'coupon' => $coupon_meta,
|
||||
'coupon_error' => $coupon_error,
|
||||
'coupon_discount' => $coupon_discount,
|
||||
'coupon_discount_label' => $coupon_discount > 0
|
||||
? ('-' . sprintf('¥%s', number_format($coupon_discount, 2, '.', '')))
|
||||
: '',
|
||||
'rebate_discount' => 0,
|
||||
'rebate_discount_label' => '',
|
||||
'points_deduction_allowed' => false,
|
||||
'card_pass_allowed' => false,
|
||||
'price_type' => '',
|
||||
'vip_level' => (int) ($priced['vip_level'] ?? 0),
|
||||
'is_vip_price' => !empty($priced['is_vip_price']),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public function create_cash_order(int $plate_id, int $user_id, string $payment_method = 'balance', string $coupon_code = '') {
|
||||
$assert = $this->assert_plate($plate_id);
|
||||
if (is_wp_error($assert)) {
|
||||
return $assert;
|
||||
}
|
||||
/** @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,
|
||||
'reload' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
$priced = $this->resolve_price($plate_id, $user_id);
|
||||
if (($priced['modo'] ?? 'money') === 'points') {
|
||||
return new WP_Error('aksrt_wrong_modo', __('该版块仅支持积分关注。', 'aksrt'), ['status' => 400]);
|
||||
}
|
||||
|
||||
$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. Guests never hit this.
|
||||
if ($vip_free) {
|
||||
return $this->create_free_paid_order($plate_id, $user_id, $post, $priced, [
|
||||
'list_amount' => 0.0,
|
||||
'payable' => 0.0,
|
||||
'coupon_code' => '',
|
||||
'coupon_discount' => 0.0,
|
||||
'coupon' => [],
|
||||
'message' => __('VIP 免费关注成功', 'aksrt'),
|
||||
'paid_note' => __('VIP 免费关注版块。', 'aksrt'),
|
||||
]);
|
||||
}
|
||||
|
||||
if ($list_amount <= 0) {
|
||||
return new WP_Error('aksrt_price_unset', __('价格未设置', 'aksrt'), ['status' => 400]);
|
||||
}
|
||||
|
||||
$discounted = $this->apply_plate_cash_discounts($priced, $plate_id, $user_id, $coupon_code, false);
|
||||
if (is_wp_error($discounted)) {
|
||||
return $discounted;
|
||||
}
|
||||
|
||||
$list_amount = round((float) ($discounted['list_amount'] ?? $list_amount), 2);
|
||||
$amount = round((float) ($discounted['payable'] ?? $list_amount), 2);
|
||||
$coupon_discount = round((float) ($discounted['coupon_discount'] ?? 0), 2);
|
||||
$coupon_meta = is_array($discounted['coupon'] ?? null) ? $discounted['coupon'] : [];
|
||||
$coupon_code_norm = (string) ($coupon_meta['code'] ?? $discounted['coupon_code'] ?? $coupon_code);
|
||||
|
||||
// Free after coupon.
|
||||
if ($amount <= 0) {
|
||||
return $this->create_free_paid_order($plate_id, $user_id, $post, $priced, [
|
||||
'list_amount' => $list_amount,
|
||||
'payable' => 0.0,
|
||||
'coupon_code' => $coupon_code_norm,
|
||||
'coupon_discount' => $coupon_discount,
|
||||
'coupon' => $coupon_meta,
|
||||
'message' => __('无需支付,已关注。', 'aksrt'),
|
||||
'paid_note' => __('优惠后无需支付。', 'aksrt'),
|
||||
]);
|
||||
}
|
||||
|
||||
if (!class_exists('AKSRT_Order') || !class_exists('AKSRT_Order_Repository')) {
|
||||
return new WP_Error('aksrt_pay_unavailable', __('支付系统不可用。', 'aksrt'), ['status' => 500]);
|
||||
}
|
||||
|
||||
$payment_method = sanitize_key($payment_method);
|
||||
if ($payment_method === '') {
|
||||
$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);
|
||||
$can_resume = false;
|
||||
if ($pending && abs((float) ($pending['amount'] ?? 0) - $amount) < 0.001) {
|
||||
$pending_meta = $this->decode_order_note_meta($pending);
|
||||
$pending_coupon = (string) ($pending_meta['coupon_code'] ?? '');
|
||||
if (is_array($pending_meta['coupon'] ?? null)) {
|
||||
$pending_coupon = (string) ($pending_meta['coupon']['code'] ?? $pending_coupon);
|
||||
}
|
||||
if ($pending_coupon === $coupon_code_norm) {
|
||||
$can_resume = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($can_resume) {
|
||||
$order_number = (string) ($pending['order_number'] ?? '');
|
||||
$order = $pending;
|
||||
} else {
|
||||
$note = wp_json_encode([
|
||||
'plate_follow_pay' => 1,
|
||||
'plate_id' => $plate_id,
|
||||
'pay_modo' => '0',
|
||||
'vip_level' => (int) ($priced['vip_level'] ?? 0),
|
||||
'base' => $base,
|
||||
'list_amount' => $list_amount,
|
||||
'amount' => $amount,
|
||||
'payable' => $amount,
|
||||
'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' => max(0, $user_id),
|
||||
'product_type' => AKSRT_Domain_Model::ORDER_PRODUCT_PLATE_FOLLOW,
|
||||
'product_id' => $plate_id,
|
||||
'amount' => $amount,
|
||||
'currency' => 'CNY',
|
||||
'status' => AKSRT_Order::STATUS_PENDING,
|
||||
'payment_method' => $payment_method,
|
||||
'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]);
|
||||
}
|
||||
|
||||
$order = AKSRT_Order_Repository::get_instance()->get($order_number);
|
||||
if (!$order) {
|
||||
return new WP_Error('aksrt_order_create_failed', __('创建订单失败。', 'aksrt'), ['status' => 500]);
|
||||
}
|
||||
}
|
||||
|
||||
$order['subject'] = sprintf(
|
||||
/* translators: %s: plate title */
|
||||
__('付费关注版块「%s」', 'aksrt'),
|
||||
get_the_title($post)
|
||||
);
|
||||
$order['payment_method'] = $payment_method;
|
||||
$order['amount'] = $amount;
|
||||
|
||||
if (!class_exists('AKSRT_Payment_Gateway')) {
|
||||
return new WP_Error('aksrt_pay_unavailable', __('支付网关不可用。', 'aksrt'), ['status' => 500]);
|
||||
}
|
||||
|
||||
$gateway_manager = AKSRT_Payment_Gateway::get_instance();
|
||||
$gateway = $gateway_manager->get_gateway($payment_method);
|
||||
if (!$gateway) {
|
||||
return new WP_Error('aksrt_gateway_unavailable', __('当前支付方式不可用。', 'aksrt'), [
|
||||
'status' => 400,
|
||||
'order_number' => $order_number,
|
||||
]);
|
||||
}
|
||||
if (method_exists($gateway, 'is_configured') && !$gateway->is_configured()) {
|
||||
return new WP_Error('aksrt_gateway_unconfigured', __('该支付方式尚未配置,请联系站长。', 'aksrt'), [
|
||||
'status' => 400,
|
||||
'order_number' => $order_number,
|
||||
]);
|
||||
}
|
||||
|
||||
$payment_result = $gateway->create_payment($order);
|
||||
if (empty($payment_result['success'])) {
|
||||
$msg = (string) ($payment_result['message'] ?? __('支付失败。', 'aksrt'));
|
||||
$code = ($payment_method === 'balance' && (str_contains($msg, '余额') || str_contains($msg, '不足')))
|
||||
? 'aksrt_balance_insufficient'
|
||||
: 'aksrt_pay_failed';
|
||||
return new WP_Error($code, $msg, [
|
||||
'status' => 402,
|
||||
'order_number' => $order_number,
|
||||
]);
|
||||
}
|
||||
|
||||
$code_url = (string) ($payment_result['payment_data']['code_url'] ?? '');
|
||||
$payment_url = (string) ($payment_result['payment_url'] ?? '');
|
||||
if ($payment_url !== '' || $code_url !== '') {
|
||||
return [
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'redirect' => true,
|
||||
'payment_url' => $payment_url,
|
||||
'qr_content' => $code_url,
|
||||
'order_number' => $order_number,
|
||||
'amount' => $amount,
|
||||
'pay_modo' => '0',
|
||||
'message' => $payment_result['message'] ?? __('请在新页面完成支付。', 'aksrt'),
|
||||
'reload' => false,
|
||||
'poll' => true,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
$paid = AKSRT_Order::get_instance()->update_status($order_number, AKSRT_Order::STATUS_PAID, [
|
||||
'payment_method' => $payment_method,
|
||||
'payment_transaction_id' => sanitize_text_field((string) ($payment_result['transaction_id'] ?? '')),
|
||||
'note' => __('用户完成版块付费关注支付。', 'aksrt'),
|
||||
]);
|
||||
if (!$paid) {
|
||||
return new WP_Error('aksrt_order_confirm_failed', __('订单确认失败。', 'aksrt'), ['status' => 500]);
|
||||
}
|
||||
|
||||
if ($user_id > 0) {
|
||||
$this->mark_followed($user_id, $plate_id);
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'order_number' => $order_number,
|
||||
'amount' => $amount,
|
||||
'pay_modo' => '0',
|
||||
'message' => $payment_result['message'] ?? __('关注成功', 'aksrt'),
|
||||
'reload' => true,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function on_order_paid(string $order_number, array $paid_order): void {
|
||||
$product_type = sanitize_key((string) ($paid_order['product_type'] ?? ''));
|
||||
if ($product_type !== AKSRT_Domain_Model::ORDER_PRODUCT_PLATE_FOLLOW) {
|
||||
return;
|
||||
}
|
||||
|
||||
$plate_id = (int) ($paid_order['product_id'] ?? 0);
|
||||
$user_id = (int) ($paid_order['user_id'] ?? 0);
|
||||
if ($plate_id <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$meta = $this->decode_order_note_meta($paid_order);
|
||||
if (empty($meta['plate_follow_pay'])) {
|
||||
$post = get_post($plate_id);
|
||||
if (!($post instanceof WP_Post) || $post->post_type !== 'aksrt_forum_plate') {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{post:WP_Post}|WP_Error
|
||||
*/
|
||||
private function assert_plate(int $plate_id) {
|
||||
$post = get_post($plate_id);
|
||||
if (!($post instanceof WP_Post) || $post->post_type !== 'aksrt_forum_plate') {
|
||||
return new WP_Error('aksrt_invalid', __('版块不存在', 'aksrt'), ['status' => 404]);
|
||||
}
|
||||
if ($post->post_status !== 'publish') {
|
||||
return new WP_Error('aksrt_invalid', __('版块不可关注', 'aksrt'), ['status' => 404]);
|
||||
}
|
||||
|
||||
if (!$this->is_follow_pay_enabled($plate_id)) {
|
||||
return new WP_Error('aksrt_not_paid', __('该版块未开启付费关注', 'aksrt'), ['status' => 400]);
|
||||
}
|
||||
|
||||
$modo = sanitize_key((string) (get_post_meta($plate_id, '_aksrt_follow_pay_modo', true) ?: 'money'));
|
||||
if ($modo === 'points') {
|
||||
return new WP_Error('aksrt_wrong_modo', __('该版块仅支持积分关注。', 'aksrt'), ['status' => 400]);
|
||||
}
|
||||
|
||||
return ['post' => $post];
|
||||
}
|
||||
|
||||
private function is_follow_pay_enabled(int $plate_id): bool {
|
||||
if (class_exists('AKSRT_Forum') && method_exists('AKSRT_Forum', 'is_plate_follow_pay_enabled')) {
|
||||
return (bool) AKSRT_Forum::get_instance()->is_plate_follow_pay_enabled($plate_id);
|
||||
}
|
||||
return (int) get_post_meta($plate_id, '_aksrt_follow_pay', true) === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{amount:float,modo:string,is_vip_price:bool,is_free:bool,label:string,base:float,vip_level:int}
|
||||
*/
|
||||
private function resolve_price(int $plate_id, int $user_id): array {
|
||||
if (class_exists('AKSRT_Forum') && method_exists('AKSRT_Forum', 'resolve_plate_follow_pay_price')) {
|
||||
return AKSRT_Forum::get_instance()->resolve_plate_follow_pay_price($plate_id, $user_id);
|
||||
}
|
||||
return [
|
||||
'amount' => max(0.0, (float) get_post_meta($plate_id, '_aksrt_follow_pay_price', true)),
|
||||
'modo' => 'money',
|
||||
'is_vip_price' => false,
|
||||
'is_free' => false,
|
||||
'label' => '',
|
||||
'base' => max(0.0, (float) get_post_meta($plate_id, '_aksrt_follow_pay_price', true)),
|
||||
'vip_level' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
private function user_has_followed(int $user_id, int $plate_id): bool {
|
||||
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);
|
||||
}
|
||||
$follow_list = get_user_meta($user_id, 'aksrt_follow_plate', true);
|
||||
if (!is_array($follow_list)) {
|
||||
return false;
|
||||
}
|
||||
return in_array($plate_id, array_map('intval', $follow_list), true);
|
||||
}
|
||||
|
||||
private function mark_followed(int $user_id, int $plate_id): void {
|
||||
if (class_exists('AKSRT_Forum') && method_exists('AKSRT_Forum', 'mark_plate_followed')) {
|
||||
AKSRT_Forum::get_instance()->mark_plate_followed($user_id, $plate_id);
|
||||
return;
|
||||
}
|
||||
if ($user_id <= 0 || $plate_id <= 0) {
|
||||
return;
|
||||
}
|
||||
$follow_list = get_user_meta($user_id, 'aksrt_follow_plate', true);
|
||||
if (!is_array($follow_list)) {
|
||||
$follow_list = [];
|
||||
}
|
||||
$follow_list[] = $plate_id;
|
||||
update_user_meta($user_id, 'aksrt_follow_plate', array_values(array_unique(array_map('intval', $follow_list))));
|
||||
}
|
||||
|
||||
/**
|
||||
* Free VIP or free-after-discount paid order.
|
||||
*
|
||||
* @param array{
|
||||
* list_amount?:float,
|
||||
* payable?:float,
|
||||
* coupon_code?:string,
|
||||
* coupon_discount?:float,
|
||||
* coupon?:array,
|
||||
* message?:string,
|
||||
* paid_note?:string
|
||||
* } $opts
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
private function create_free_paid_order(int $plate_id, int $user_id, WP_Post $post, array $priced, array $opts = []) {
|
||||
$list_amount = round((float) ($opts['list_amount'] ?? 0), 2);
|
||||
$payable = round((float) ($opts['payable'] ?? 0), 2);
|
||||
$coupon_code = sanitize_text_field((string) ($opts['coupon_code'] ?? ''));
|
||||
$coupon_discount = round((float) ($opts['coupon_discount'] ?? 0), 2);
|
||||
$coupon_meta = is_array($opts['coupon'] ?? null) ? $opts['coupon'] : [];
|
||||
$message = (string) ($opts['message'] ?? __('VIP 免费关注成功', 'aksrt'));
|
||||
$paid_note = (string) ($opts['paid_note'] ?? __('VIP 免费关注版块。', 'aksrt'));
|
||||
|
||||
if (!class_exists('AKSRT_Order') || !class_exists('AKSRT_Order_Repository')) {
|
||||
$this->mark_followed($user_id, $plate_id);
|
||||
return [
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'order_number' => '',
|
||||
'amount' => 0,
|
||||
'pay_modo' => '0',
|
||||
'free' => true,
|
||||
'message' => $message,
|
||||
'reload' => true,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
$note = wp_json_encode([
|
||||
'plate_follow_pay' => 1,
|
||||
'plate_id' => $plate_id,
|
||||
'pay_modo' => '0',
|
||||
'vip_level' => (int) ($priced['vip_level'] ?? 0),
|
||||
'base' => (float) ($priced['base'] ?? 0),
|
||||
'list_amount' => $list_amount,
|
||||
'amount' => $payable,
|
||||
'payable' => $payable,
|
||||
'coupon_code' => $coupon_code,
|
||||
'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' => max(0, $user_id),
|
||||
'product_type' => AKSRT_Domain_Model::ORDER_PRODUCT_PLATE_FOLLOW,
|
||||
'product_id' => $plate_id,
|
||||
'amount' => 0.0,
|
||||
'currency' => 'CNY',
|
||||
'status' => AKSRT_Order::STATUS_PENDING,
|
||||
'payment_method' => 'free',
|
||||
'note' => is_string($note) ? $note : '',
|
||||
'expires_at' => gmdate('Y-m-d H:i:s', time() + 30 * MINUTE_IN_SECONDS),
|
||||
]);
|
||||
|
||||
if ($order_number === '') {
|
||||
$this->mark_followed($user_id, $plate_id);
|
||||
return [
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'order_number' => '',
|
||||
'amount' => 0,
|
||||
'free' => true,
|
||||
'message' => $message,
|
||||
'reload' => true,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
$paid = AKSRT_Order::get_instance()->update_status($order_number, AKSRT_Order::STATUS_PAID, [
|
||||
'payment_method' => 'free',
|
||||
'payment_transaction_id' => 'free_' . $order_number,
|
||||
'note' => $paid_note,
|
||||
]);
|
||||
if (!$paid) {
|
||||
return new WP_Error('aksrt_order_confirm_failed', __('订单确认失败。', 'aksrt'), ['status' => 500]);
|
||||
}
|
||||
$this->mark_followed($user_id, $plate_id);
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'order_number' => $order_number,
|
||||
'amount' => 0,
|
||||
'pay_modo' => '0',
|
||||
'free' => true,
|
||||
'message' => $message,
|
||||
'reload' => true,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
private function is_coupon_allowed(int $plate_id): bool {
|
||||
return (int) get_post_meta($plate_id, '_aksrt_follow_pay_coupon_s', true) === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* VIP list price → coupon → payable.
|
||||
*
|
||||
* @param array<string,mixed> $priced
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
private function apply_plate_cash_discounts(array $priced, int $plate_id, int $user_id, string $coupon_code = '', bool $soft_coupon = false) {
|
||||
$list_amount = round(max(0.0, (float) ($priced['amount'] ?? 0)), 2);
|
||||
$pay = $list_amount;
|
||||
$coupon_discount = 0.0;
|
||||
$coupon_meta = [];
|
||||
$coupon_error = '';
|
||||
$coupon_code = sanitize_text_field($coupon_code);
|
||||
|
||||
if ($coupon_code !== '') {
|
||||
if (!$this->is_coupon_allowed($plate_id)) {
|
||||
$err = new WP_Error('aksrt_coupon_not_allowed', __('当前版块不支持优惠码。', 'aksrt'), ['status' => 400]);
|
||||
if (!$soft_coupon) {
|
||||
return $err;
|
||||
}
|
||||
$coupon_error = $err->get_error_message();
|
||||
$coupon_code = '';
|
||||
} elseif (!class_exists('AKSRT_Coupon')) {
|
||||
$err = new WP_Error('aksrt_coupon_unavailable', __('优惠券系统不可用。', 'aksrt'), ['status' => 500]);
|
||||
if (!$soft_coupon) {
|
||||
return $err;
|
||||
}
|
||||
$coupon_error = $err->get_error_message();
|
||||
$coupon_code = '';
|
||||
} else {
|
||||
$result = AKSRT_Coupon::get_instance()->validate($coupon_code, $pay, [
|
||||
'user_id' => max(0, $user_id),
|
||||
'product_ids' => [$plate_id],
|
||||
'context' => 'plate_follow',
|
||||
]);
|
||||
if (empty($result['valid'])) {
|
||||
$err = new WP_Error(
|
||||
'aksrt_coupon_error',
|
||||
(string) ($result['message'] ?? __('优惠码不可用。', 'aksrt')),
|
||||
['status' => 400]
|
||||
);
|
||||
if (!$soft_coupon) {
|
||||
return $err;
|
||||
}
|
||||
$coupon_error = $err->get_error_message();
|
||||
$coupon_code = '';
|
||||
} else {
|
||||
$coupon_discount = round(max(0.0, (float) ($result['discount'] ?? 0)), 2);
|
||||
if ($coupon_discount > $pay) {
|
||||
$coupon_discount = $pay;
|
||||
}
|
||||
$pay = round(max(0.0, $pay - $coupon_discount), 2);
|
||||
$c = is_array($result['coupon'] ?? null) ? $result['coupon'] : [];
|
||||
$coupon_meta = [
|
||||
'id' => (int) ($c['id'] ?? 0),
|
||||
'code' => (string) ($c['code'] ?? $coupon_code),
|
||||
'type' => (string) ($c['type'] ?? ''),
|
||||
'value' => (float) ($c['value'] ?? 0),
|
||||
'discount' => $coupon_discount,
|
||||
];
|
||||
$coupon_code = (string) $coupon_meta['code'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'list_amount' => $list_amount,
|
||||
'coupon_discount' => $coupon_discount,
|
||||
'coupon' => $coupon_meta,
|
||||
'coupon_code' => $coupon_code,
|
||||
'coupon_error' => $coupon_error,
|
||||
'payable' => round(max(0.0, $pay), 2),
|
||||
];
|
||||
}
|
||||
|
||||
private function finalize_plate_order_side_effects(string $order_number, array $order): void {
|
||||
$meta = $this->decode_order_note_meta($order);
|
||||
if (empty($meta['plate_follow_pay'])) {
|
||||
return;
|
||||
}
|
||||
if (($meta['pay_modo'] ?? '') === 'points') {
|
||||
return;
|
||||
}
|
||||
$buyer_id = (int) ($order['user_id'] ?? 0);
|
||||
if (function_exists('aksrt_burn_order_coupon')) {
|
||||
aksrt_burn_order_coupon($order_number, $meta, $buyer_id);
|
||||
}
|
||||
}
|
||||
|
||||
private function find_pending_order(int $user_id, int $plate_id): ?array {
|
||||
if ($user_id <= 0 || $plate_id <= 0 || !class_exists('AKSRT_Order_Repository')) {
|
||||
return null;
|
||||
}
|
||||
$orders = AKSRT_Order_Repository::get_instance()->get_user_orders($user_id, [
|
||||
'product_type' => AKSRT_Domain_Model::ORDER_PRODUCT_PLATE_FOLLOW,
|
||||
'status' => AKSRT_Domain_Model::ORDER_STATUS_PENDING,
|
||||
'limit' => 20,
|
||||
'offset' => 0,
|
||||
]);
|
||||
foreach ($orders as $order) {
|
||||
if ((int) ($order['product_id'] ?? 0) !== $plate_id) {
|
||||
continue;
|
||||
}
|
||||
$meta = $this->decode_order_note_meta($order);
|
||||
if (!empty($meta['plate_follow_pay'])) {
|
||||
return $order;
|
||||
}
|
||||
return $order;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private function decode_order_note_meta(array $order): array {
|
||||
if (isset($order['meta_payload']) && is_array($order['meta_payload']) && $order['meta_payload'] !== []) {
|
||||
return $order['meta_payload'];
|
||||
}
|
||||
$note = (string) ($order['note'] ?? '');
|
||||
if ($note === '') {
|
||||
return [];
|
||||
}
|
||||
$decoded = json_decode($note, true);
|
||||
return is_array($decoded) ? $decoded : [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,873 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT Topic Pay — forum topic multi-gateway cashier.
|
||||
*
|
||||
* Contract:
|
||||
* - visibility = pay topics only (credits path stays on unlock_topic_with_credits)
|
||||
* - product_type = resource, product_id = topic_id
|
||||
* - order note.topic_pay = 1
|
||||
* - unlock: has_user_paid_topic (order OR unlock list OR VIP free)
|
||||
* - coupon: _aksrt_topic_coupon_s + AKSRT_Coupon (cash only; no affiliate stack)
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class AKSRT_Topic_Pay {
|
||||
|
||||
private static $instance = null;
|
||||
|
||||
public static function get_instance(): self {
|
||||
if (null === self::$instance) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
public function init(): void {
|
||||
add_action('rest_api_init', [$this, 'register_routes']);
|
||||
add_action('aksrt_order_paid', [$this, 'on_order_paid'], 16, 2);
|
||||
}
|
||||
|
||||
public function register_routes(): void {
|
||||
register_rest_route('aksrt/v1', '/pay/topic/(?P<id>\\d+)', [
|
||||
[
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'rest_preview'],
|
||||
'permission_callback' => [$this, 'check_rest_nonce'],
|
||||
],
|
||||
[
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'rest_purchase'],
|
||||
'permission_callback' => [$this, 'check_rest_nonce'],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WP_REST_Request $request
|
||||
* @return true|WP_Error
|
||||
*/
|
||||
public function check_rest_nonce(WP_REST_Request $request) {
|
||||
$nonce = $request->get_header('x-wp-nonce');
|
||||
if (!$nonce) {
|
||||
return new WP_Error('aksrt_rest_no_nonce', __('缺少安全令牌', 'aksrt'), ['status' => 403]);
|
||||
}
|
||||
if (!wp_verify_nonce($nonce, 'wp_rest')) {
|
||||
return new WP_Error('aksrt_rest_bad_nonce', __('安全令牌已过期,请刷新页面', 'aksrt'), ['status' => 403]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rest_preview(WP_REST_Request $request) {
|
||||
$topic_id = (int) $request['id'];
|
||||
$uid = get_current_user_id();
|
||||
$coupon_code = sanitize_text_field((string) ($request->get_param('coupon_code') ?? $request->get_param('coupon') ?? ''));
|
||||
$preview = $this->preview($topic_id, $uid, $coupon_code);
|
||||
if (is_wp_error($preview)) {
|
||||
return $preview;
|
||||
}
|
||||
return new WP_REST_Response($preview, 200);
|
||||
}
|
||||
|
||||
public function rest_purchase(WP_REST_Request $request) {
|
||||
$topic_id = (int) $request['id'];
|
||||
$uid = get_current_user_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('/')),
|
||||
]);
|
||||
}
|
||||
|
||||
$params = $request->get_json_params();
|
||||
if (!is_array($params)) {
|
||||
$params = $request->get_params();
|
||||
}
|
||||
|
||||
$payment_method = sanitize_key((string) ($params['payment_method'] ?? 'balance'));
|
||||
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(
|
||||
$this->create_cash_order($topic_id, $uid, $payment_method, $coupon_code)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $result
|
||||
* @return WP_REST_Response|WP_Error
|
||||
*/
|
||||
private function rest_wrap_result($result) {
|
||||
if (is_wp_error($result)) {
|
||||
return $result;
|
||||
}
|
||||
if (empty($result['success'])) {
|
||||
$code = sanitize_key((string) ($result['code'] ?? 'aksrt_pay_failed'));
|
||||
$status = (int) ($result['status'] ?? 400);
|
||||
return new WP_Error(
|
||||
$code !== '' ? $code : 'aksrt_pay_failed',
|
||||
(string) ($result['message'] ?? __('支付失败', 'aksrt')),
|
||||
array_merge(
|
||||
['status' => $status > 0 ? $status : 400],
|
||||
is_array($result['data'] ?? null) ? $result['data'] : []
|
||||
)
|
||||
);
|
||||
}
|
||||
return new WP_REST_Response($result, 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cashier preview payload (content-pay field subset for cms-download).
|
||||
*
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public function preview(int $topic_id, int $user_id = 0, string $coupon_code = '') {
|
||||
$assert = $this->assert_topic($topic_id);
|
||||
if (is_wp_error($assert)) {
|
||||
return $assert;
|
||||
}
|
||||
/** @var WP_Post $post */
|
||||
$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 && !$allow_guest) {
|
||||
return [
|
||||
'post_id' => $topic_id,
|
||||
'title' => get_the_title($post),
|
||||
'permalink' => get_permalink($post),
|
||||
'pay_modo' => '0',
|
||||
'pay_type' => 'topic',
|
||||
'pay_type_label' => __('付费主题', 'aksrt'),
|
||||
'amount' => 0,
|
||||
'base' => 0,
|
||||
'list_amount' => 0,
|
||||
'payable' => 0,
|
||||
'currency' => '¥',
|
||||
'amount_label' => '',
|
||||
'base_label' => '',
|
||||
'list_amount_label' => '',
|
||||
'is_free' => false,
|
||||
'has_paid_access' => false,
|
||||
'need_login' => true,
|
||||
'allow_guest' => false,
|
||||
'need_vip' => false,
|
||||
'need_purchase' => true,
|
||||
'logged_in' => false,
|
||||
'balance' => 0,
|
||||
'balance_enough' => false,
|
||||
'payment_methods' => [],
|
||||
'login_url' => wp_login_url(get_permalink($post)),
|
||||
'vip_url' => home_url('/'),
|
||||
'credits_url' => home_url('/'),
|
||||
'coupon_allowed' => false,
|
||||
'coupon_desc' => '',
|
||||
'coupon_code' => '',
|
||||
'coupon' => null,
|
||||
'coupon_error' => '',
|
||||
'coupon_discount' => 0,
|
||||
'coupon_discount_label' => '',
|
||||
'rebate_discount' => 0,
|
||||
'rebate_discount_label' => '',
|
||||
'points_deduction_allowed' => false,
|
||||
'card_pass_allowed' => false,
|
||||
'price_type' => '',
|
||||
];
|
||||
}
|
||||
|
||||
$priced = $this->resolve_price($topic_id, $uid);
|
||||
$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 ($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($uid > 0)
|
||||
: ($uid > 0
|
||||
? [['id' => 'balance', 'name' => __('站内余额', 'aksrt')]]
|
||||
: [['id' => 'alipay', 'name' => __('支付宝', 'aksrt')]]);
|
||||
|
||||
$coupon_allowed = $this->is_coupon_allowed($topic_id);
|
||||
$coupon_error = '';
|
||||
$coupon_discount = 0.0;
|
||||
$coupon_meta = null;
|
||||
$applied_code = '';
|
||||
$payable = $list_amount;
|
||||
|
||||
if (!$vip_free && $list_amount > 0) {
|
||||
$discounted = $this->apply_topic_cash_discounts($priced, $topic_id, $uid, $coupon_code, true);
|
||||
if (is_wp_error($discounted)) {
|
||||
$coupon_error = $discounted->get_error_message();
|
||||
$discounted = $this->apply_topic_cash_discounts($priced, $topic_id, $uid, '', true);
|
||||
}
|
||||
if (!is_wp_error($discounted)) {
|
||||
$list_amount = round((float) ($discounted['list_amount'] ?? $list_amount), 2);
|
||||
$payable = round((float) ($discounted['payable'] ?? $list_amount), 2);
|
||||
$coupon_discount = round((float) ($discounted['coupon_discount'] ?? 0), 2);
|
||||
$coupon_meta = !empty($discounted['coupon']) ? $discounted['coupon'] : null;
|
||||
$applied_code = (string) ($discounted['coupon_code'] ?? ($coupon_meta['code'] ?? ''));
|
||||
if ($coupon_error === '' && !empty($discounted['coupon_error'])) {
|
||||
$coupon_error = (string) $discounted['coupon_error'];
|
||||
}
|
||||
}
|
||||
} elseif ($vip_free) {
|
||||
$payable = 0.0;
|
||||
$list_amount = 0.0;
|
||||
}
|
||||
|
||||
$is_free = $vip_free || $payable <= 0;
|
||||
$amount_label = $is_free
|
||||
? __('免费', 'aksrt')
|
||||
: sprintf('¥%s', number_format($payable, 2, '.', ''));
|
||||
$list_label = sprintf('¥%s', number_format($list_amount > 0 ? $list_amount : $base, 2, '.', ''));
|
||||
$base_label = sprintf('¥%s', number_format($base, 2, '.', ''));
|
||||
|
||||
return [
|
||||
'post_id' => $topic_id,
|
||||
'title' => get_the_title($post),
|
||||
'permalink' => get_permalink($post),
|
||||
'pay_modo' => '0',
|
||||
'pay_type' => 'topic',
|
||||
'pay_type_label' => __('付费主题', 'aksrt'),
|
||||
'amount' => $payable,
|
||||
'base' => $base,
|
||||
'list_amount' => $list_amount > 0 ? $list_amount : $base,
|
||||
'list_amount_label' => $list_label,
|
||||
'payable' => $payable,
|
||||
'currency' => '¥',
|
||||
'amount_label' => $amount_label,
|
||||
'base_label' => $base_label,
|
||||
'is_free' => $is_free,
|
||||
'has_paid_access' => $has_paid,
|
||||
'paid_order' => null,
|
||||
'pending_order' => null,
|
||||
'need_login' => $uid <= 0 && !$allow_guest,
|
||||
'allow_guest' => $allow_guest,
|
||||
'need_vip' => false,
|
||||
'need_purchase' => !$has_paid && !$is_free,
|
||||
'logged_in' => $uid > 0,
|
||||
'balance' => $balance,
|
||||
'balance_enough' => true,
|
||||
'payment_methods' => $methods,
|
||||
'login_url' => wp_login_url(get_permalink($post)),
|
||||
'vip_url' => home_url('/'),
|
||||
'credits_url' => home_url('/'),
|
||||
'coupon_allowed' => $coupon_allowed,
|
||||
'coupon_desc' => $coupon_allowed ? __('支持优惠码', 'aksrt') : '',
|
||||
'coupon_code' => $applied_code,
|
||||
'coupon' => $coupon_meta,
|
||||
'coupon_error' => $coupon_error,
|
||||
'coupon_discount' => $coupon_discount,
|
||||
'coupon_discount_label' => $coupon_discount > 0
|
||||
? ('-' . sprintf('¥%s', number_format($coupon_discount, 2, '.', '')))
|
||||
: '',
|
||||
'rebate_discount' => 0,
|
||||
'rebate_discount_label' => '',
|
||||
'points_deduction_allowed' => false,
|
||||
'card_pass_allowed' => false,
|
||||
'price_type' => '',
|
||||
'vip_level' => (int) ($priced['vip_level'] ?? 0),
|
||||
'is_vip_price' => !empty($priced['is_vip_price']),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create cash order and invoke gateway.
|
||||
*
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public function create_cash_order(int $topic_id, int $user_id, string $payment_method = 'balance', string $coupon_code = '') {
|
||||
$assert = $this->assert_topic($topic_id);
|
||||
if (is_wp_error($assert)) {
|
||||
return $assert;
|
||||
}
|
||||
/** @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,
|
||||
'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. Guests never hit this.
|
||||
if ($vip_free) {
|
||||
return $this->create_free_paid_order($topic_id, $user_id, $post, $priced, [
|
||||
'list_amount' => 0.0,
|
||||
'payable' => 0.0,
|
||||
'coupon_code' => '',
|
||||
'coupon_discount' => 0.0,
|
||||
'coupon' => [],
|
||||
'message' => __('VIP 免费解锁成功', 'aksrt'),
|
||||
'paid_note' => __('VIP 免费解锁主题。', 'aksrt'),
|
||||
]);
|
||||
}
|
||||
|
||||
if ($list_amount <= 0) {
|
||||
return new WP_Error('aksrt_price_unset', __('价格未设置', 'aksrt'), ['status' => 400]);
|
||||
}
|
||||
|
||||
$discounted = $this->apply_topic_cash_discounts($priced, $topic_id, $user_id, $coupon_code, false);
|
||||
if (is_wp_error($discounted)) {
|
||||
return $discounted;
|
||||
}
|
||||
|
||||
$list_amount = round((float) ($discounted['list_amount'] ?? $list_amount), 2);
|
||||
$amount = round((float) ($discounted['payable'] ?? $list_amount), 2);
|
||||
$coupon_discount = round((float) ($discounted['coupon_discount'] ?? 0), 2);
|
||||
$coupon_meta = is_array($discounted['coupon'] ?? null) ? $discounted['coupon'] : [];
|
||||
$coupon_code_norm = (string) ($coupon_meta['code'] ?? $discounted['coupon_code'] ?? $coupon_code);
|
||||
|
||||
// Free after coupon discount.
|
||||
if ($amount <= 0) {
|
||||
return $this->create_free_paid_order($topic_id, $user_id, $post, $priced, [
|
||||
'list_amount' => $list_amount,
|
||||
'payable' => 0.0,
|
||||
'coupon_code' => $coupon_code_norm,
|
||||
'coupon_discount' => $coupon_discount,
|
||||
'coupon' => $coupon_meta,
|
||||
'message' => __('无需支付,已可访问。', 'aksrt'),
|
||||
'paid_note' => __('优惠后无需支付。', 'aksrt'),
|
||||
]);
|
||||
}
|
||||
|
||||
if (!class_exists('AKSRT_Order') || !class_exists('AKSRT_Order_Repository')) {
|
||||
return new WP_Error('aksrt_pay_unavailable', __('支付系统不可用。', 'aksrt'), ['status' => 500]);
|
||||
}
|
||||
|
||||
$payment_method = sanitize_key($payment_method);
|
||||
if ($payment_method === '') {
|
||||
$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);
|
||||
$can_resume = false;
|
||||
if ($pending && abs((float) ($pending['amount'] ?? 0) - $amount) < 0.001) {
|
||||
$pending_meta = $this->decode_order_note_meta($pending);
|
||||
$pending_coupon = (string) ($pending_meta['coupon_code'] ?? '');
|
||||
if (is_array($pending_meta['coupon'] ?? null)) {
|
||||
$pending_coupon = (string) ($pending_meta['coupon']['code'] ?? $pending_coupon);
|
||||
}
|
||||
if ($pending_coupon === $coupon_code_norm) {
|
||||
$can_resume = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($can_resume) {
|
||||
$order_number = (string) ($pending['order_number'] ?? '');
|
||||
$order = $pending;
|
||||
} else {
|
||||
$note = wp_json_encode([
|
||||
'topic_pay' => 1,
|
||||
'topic_id' => $topic_id,
|
||||
'pay_modo' => '0',
|
||||
'vip_level' => (int) ($priced['vip_level'] ?? 0),
|
||||
'base' => $base,
|
||||
'list_amount' => $list_amount,
|
||||
'amount' => $amount,
|
||||
'payable' => $amount,
|
||||
'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' => max(0, $user_id),
|
||||
'product_type' => AKSRT_Domain_Model::ORDER_PRODUCT_RESOURCE,
|
||||
'product_id' => $topic_id,
|
||||
'amount' => $amount,
|
||||
'currency' => 'CNY',
|
||||
'status' => AKSRT_Order::STATUS_PENDING,
|
||||
'payment_method' => $payment_method,
|
||||
'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]);
|
||||
}
|
||||
|
||||
$order = AKSRT_Order_Repository::get_instance()->get($order_number);
|
||||
if (!$order) {
|
||||
return new WP_Error('aksrt_order_create_failed', __('创建订单失败。', 'aksrt'), ['status' => 500]);
|
||||
}
|
||||
}
|
||||
|
||||
$order['subject'] = sprintf(
|
||||
/* translators: %s: topic title */
|
||||
__('购买论坛主题「%s」', 'aksrt'),
|
||||
get_the_title($post)
|
||||
);
|
||||
$order['payment_method'] = $payment_method;
|
||||
$order['amount'] = $amount;
|
||||
|
||||
if (!class_exists('AKSRT_Payment_Gateway')) {
|
||||
return new WP_Error('aksrt_pay_unavailable', __('支付网关不可用。', 'aksrt'), ['status' => 500]);
|
||||
}
|
||||
|
||||
$gateway_manager = AKSRT_Payment_Gateway::get_instance();
|
||||
$gateway = $gateway_manager->get_gateway($payment_method);
|
||||
if (!$gateway) {
|
||||
return new WP_Error('aksrt_gateway_unavailable', __('当前支付方式不可用。', 'aksrt'), [
|
||||
'status' => 400,
|
||||
'order_number' => $order_number,
|
||||
]);
|
||||
}
|
||||
if (method_exists($gateway, 'is_configured') && !$gateway->is_configured()) {
|
||||
return new WP_Error('aksrt_gateway_unconfigured', __('该支付方式尚未配置,请联系站长。', 'aksrt'), [
|
||||
'status' => 400,
|
||||
'order_number' => $order_number,
|
||||
]);
|
||||
}
|
||||
|
||||
$payment_result = $gateway->create_payment($order);
|
||||
if (empty($payment_result['success'])) {
|
||||
$msg = (string) ($payment_result['message'] ?? __('支付失败。', 'aksrt'));
|
||||
$code = ($payment_method === 'balance' && (str_contains($msg, '余额') || str_contains($msg, '不足')))
|
||||
? 'aksrt_balance_insufficient'
|
||||
: 'aksrt_pay_failed';
|
||||
return new WP_Error($code, $msg, [
|
||||
'status' => 402,
|
||||
'order_number' => $order_number,
|
||||
]);
|
||||
}
|
||||
|
||||
$code_url = (string) ($payment_result['payment_data']['code_url'] ?? '');
|
||||
$payment_url = (string) ($payment_result['payment_url'] ?? '');
|
||||
if ($payment_url !== '' || $code_url !== '') {
|
||||
return [
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'redirect' => true,
|
||||
'payment_url' => $payment_url,
|
||||
'qr_content' => $code_url,
|
||||
'order_number' => $order_number,
|
||||
'amount' => $amount,
|
||||
'pay_modo' => '0',
|
||||
'message' => $payment_result['message'] ?? __('请在新页面完成支付。', 'aksrt'),
|
||||
'reload' => false,
|
||||
'poll' => true,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
// Sync gateway (balance): mark paid.
|
||||
$paid = AKSRT_Order::get_instance()->update_status($order_number, AKSRT_Order::STATUS_PAID, [
|
||||
'payment_method' => $payment_method,
|
||||
'payment_transaction_id' => sanitize_text_field((string) ($payment_result['transaction_id'] ?? '')),
|
||||
'note' => __('用户完成论坛主题购买支付。', 'aksrt'),
|
||||
]);
|
||||
if (!$paid) {
|
||||
return new WP_Error('aksrt_order_confirm_failed', __('订单确认失败。', 'aksrt'), ['status' => 500]);
|
||||
}
|
||||
|
||||
if ($user_id > 0) {
|
||||
$this->mark_unlocked($user_id, $topic_id);
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'order_number' => $order_number,
|
||||
'amount' => $amount,
|
||||
'pay_modo' => '0',
|
||||
'message' => $payment_result['message'] ?? __('支付成功', 'aksrt'),
|
||||
'reload' => true,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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'] ?? ''));
|
||||
if ($product_type !== AKSRT_Domain_Model::ORDER_PRODUCT_RESOURCE) {
|
||||
return;
|
||||
}
|
||||
|
||||
$topic_id = (int) ($paid_order['product_id'] ?? 0);
|
||||
$user_id = (int) ($paid_order['user_id'] ?? 0);
|
||||
if ($topic_id <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$meta = $this->decode_order_note_meta($paid_order);
|
||||
$is_topic_pay = !empty($meta['topic_pay']);
|
||||
if (!$is_topic_pay) {
|
||||
$post = get_post($topic_id);
|
||||
if (!($post instanceof WP_Post) || $post->post_type !== 'aksrt_forum_topic') {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{post:WP_Post}|WP_Error
|
||||
*/
|
||||
private function assert_topic(int $topic_id) {
|
||||
$post = get_post($topic_id);
|
||||
if (!($post instanceof WP_Post) || $post->post_type !== 'aksrt_forum_topic') {
|
||||
return new WP_Error('aksrt_invalid', __('主题不存在', 'aksrt'), ['status' => 404]);
|
||||
}
|
||||
if ($post->post_status !== 'publish') {
|
||||
return new WP_Error('aksrt_invalid', __('主题不可购买', 'aksrt'), ['status' => 404]);
|
||||
}
|
||||
|
||||
$visibility = sanitize_key((string) get_post_meta($topic_id, '_aksrt_visibility', true));
|
||||
if ($visibility !== AKSRT_Forum::VISIBILITY_PAY) {
|
||||
return new WP_Error('aksrt_not_paid', __('该主题不是付费可见内容', 'aksrt'), ['status' => 400]);
|
||||
}
|
||||
|
||||
return ['post' => $post];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{amount:float,is_vip_price:bool,is_free:bool,label:string,base:float,vip_set:bool,vip_level:int}
|
||||
*/
|
||||
private function resolve_price(int $topic_id, int $user_id): array {
|
||||
if (class_exists('AKSRT_Forum')) {
|
||||
return AKSRT_Forum::get_instance()->resolve_topic_pay_price($topic_id, $user_id);
|
||||
}
|
||||
return [
|
||||
'amount' => max(0.0, (float) get_post_meta($topic_id, '_aksrt_pay_price', true)),
|
||||
'is_vip_price' => false,
|
||||
'is_free' => false,
|
||||
'label' => '',
|
||||
'base' => max(0.0, (float) get_post_meta($topic_id, '_aksrt_pay_price', true)),
|
||||
'vip_set' => false,
|
||||
'vip_level' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
if (class_exists('AKSRT_Order')) {
|
||||
return AKSRT_Order::get_instance()->has_paid_access(
|
||||
$user_id,
|
||||
AKSRT_Domain_Model::ORDER_PRODUCT_RESOURCE,
|
||||
$topic_id
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private function mark_unlocked(int $user_id, int $topic_id): void {
|
||||
if (class_exists('AKSRT_Forum') && method_exists('AKSRT_Forum', 'mark_topic_unlocked')) {
|
||||
AKSRT_Forum::get_instance()->mark_topic_unlocked($user_id, $topic_id);
|
||||
return;
|
||||
}
|
||||
if ($user_id <= 0 || $topic_id <= 0) {
|
||||
return;
|
||||
}
|
||||
$unlocked = get_user_meta($user_id, 'aksrt_unlocked_topics', true);
|
||||
if (!is_array($unlocked)) {
|
||||
$unlocked = [];
|
||||
}
|
||||
$unlocked[] = $topic_id;
|
||||
update_user_meta($user_id, 'aksrt_unlocked_topics', array_values(array_unique(array_map('intval', $unlocked))));
|
||||
}
|
||||
|
||||
private function is_coupon_allowed(int $topic_id): bool {
|
||||
return (int) get_post_meta($topic_id, '_aksrt_topic_coupon_s', true) === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* VIP list price → coupon → payable. No affiliate rebate on topics.
|
||||
*
|
||||
* @param array<string,mixed> $priced
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
private function apply_topic_cash_discounts(array $priced, int $topic_id, int $user_id, string $coupon_code = '', bool $soft_coupon = false) {
|
||||
$list_amount = round(max(0.0, (float) ($priced['amount'] ?? 0)), 2);
|
||||
$pay = $list_amount;
|
||||
$coupon_discount = 0.0;
|
||||
$coupon_meta = [];
|
||||
$coupon_error = '';
|
||||
$coupon_code = sanitize_text_field($coupon_code);
|
||||
|
||||
if ($coupon_code !== '') {
|
||||
if (!$this->is_coupon_allowed($topic_id)) {
|
||||
$err = new WP_Error('aksrt_coupon_not_allowed', __('当前主题不支持优惠码。', 'aksrt'), ['status' => 400]);
|
||||
if (!$soft_coupon) {
|
||||
return $err;
|
||||
}
|
||||
$coupon_error = $err->get_error_message();
|
||||
$coupon_code = '';
|
||||
} elseif (!class_exists('AKSRT_Coupon')) {
|
||||
$err = new WP_Error('aksrt_coupon_unavailable', __('优惠券系统不可用。', 'aksrt'), ['status' => 500]);
|
||||
if (!$soft_coupon) {
|
||||
return $err;
|
||||
}
|
||||
$coupon_error = $err->get_error_message();
|
||||
$coupon_code = '';
|
||||
} else {
|
||||
$result = AKSRT_Coupon::get_instance()->validate($coupon_code, $pay, [
|
||||
'user_id' => max(0, $user_id),
|
||||
'product_ids' => [$topic_id],
|
||||
'context' => 'topic',
|
||||
]);
|
||||
if (empty($result['valid'])) {
|
||||
$err = new WP_Error(
|
||||
'aksrt_coupon_error',
|
||||
(string) ($result['message'] ?? __('优惠码不可用。', 'aksrt')),
|
||||
['status' => 400]
|
||||
);
|
||||
if (!$soft_coupon) {
|
||||
return $err;
|
||||
}
|
||||
$coupon_error = $err->get_error_message();
|
||||
$coupon_code = '';
|
||||
} else {
|
||||
$coupon_discount = round(max(0.0, (float) ($result['discount'] ?? 0)), 2);
|
||||
if ($coupon_discount > $pay) {
|
||||
$coupon_discount = $pay;
|
||||
}
|
||||
$pay = round(max(0.0, $pay - $coupon_discount), 2);
|
||||
$c = is_array($result['coupon'] ?? null) ? $result['coupon'] : [];
|
||||
$coupon_meta = [
|
||||
'id' => (int) ($c['id'] ?? 0),
|
||||
'code' => (string) ($c['code'] ?? $coupon_code),
|
||||
'type' => (string) ($c['type'] ?? ''),
|
||||
'value' => (float) ($c['value'] ?? 0),
|
||||
'discount' => $coupon_discount,
|
||||
];
|
||||
$coupon_code = (string) $coupon_meta['code'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'list_amount' => $list_amount,
|
||||
'coupon_discount' => $coupon_discount,
|
||||
'coupon' => $coupon_meta,
|
||||
'coupon_code' => $coupon_code,
|
||||
'coupon_error' => $coupon_error,
|
||||
'payable' => round(max(0.0, $pay), 2),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Burn coupon after paid (idempotent via coupon_usage.order_number).
|
||||
*/
|
||||
private function finalize_topic_order_side_effects(string $order_number, array $order): void {
|
||||
$meta = $this->decode_order_note_meta($order);
|
||||
if (empty($meta['topic_pay'])) {
|
||||
return;
|
||||
}
|
||||
if (($meta['pay_modo'] ?? '') === 'points') {
|
||||
return;
|
||||
}
|
||||
|
||||
$buyer_id = (int) ($order['user_id'] ?? 0);
|
||||
if (function_exists('aksrt_burn_order_coupon')) {
|
||||
aksrt_burn_order_coupon($order_number, $meta, $buyer_id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Free VIP or free-after-discount paid order.
|
||||
*
|
||||
* @param array{
|
||||
* list_amount?:float,
|
||||
* payable?:float,
|
||||
* coupon_code?:string,
|
||||
* coupon_discount?:float,
|
||||
* coupon?:array,
|
||||
* message?:string,
|
||||
* paid_note?:string
|
||||
* } $opts
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
private function create_free_paid_order(int $topic_id, int $user_id, WP_Post $post, array $priced, array $opts = []) {
|
||||
$list_amount = round((float) ($opts['list_amount'] ?? 0), 2);
|
||||
$payable = round((float) ($opts['payable'] ?? 0), 2);
|
||||
$coupon_code = sanitize_text_field((string) ($opts['coupon_code'] ?? ''));
|
||||
$coupon_discount = round((float) ($opts['coupon_discount'] ?? 0), 2);
|
||||
$coupon_meta = is_array($opts['coupon'] ?? null) ? $opts['coupon'] : [];
|
||||
$message = (string) ($opts['message'] ?? __('VIP 免费解锁成功', 'aksrt'));
|
||||
$paid_note = (string) ($opts['paid_note'] ?? __('VIP 免费解锁主题。', 'aksrt'));
|
||||
|
||||
if (!class_exists('AKSRT_Order') || !class_exists('AKSRT_Order_Repository')) {
|
||||
$this->mark_unlocked($user_id, $topic_id);
|
||||
return [
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'order_number' => '',
|
||||
'amount' => 0,
|
||||
'pay_modo' => '0',
|
||||
'free' => true,
|
||||
'message' => $message,
|
||||
'reload' => true,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
$note = wp_json_encode([
|
||||
'topic_pay' => 1,
|
||||
'topic_id' => $topic_id,
|
||||
'pay_modo' => '0',
|
||||
'vip_level' => (int) ($priced['vip_level'] ?? 0),
|
||||
'base' => (float) ($priced['base'] ?? 0),
|
||||
'list_amount' => $list_amount,
|
||||
'amount' => $payable,
|
||||
'payable' => $payable,
|
||||
'coupon_code' => $coupon_code,
|
||||
'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' => max(0, $user_id),
|
||||
'product_type' => AKSRT_Domain_Model::ORDER_PRODUCT_RESOURCE,
|
||||
'product_id' => $topic_id,
|
||||
'amount' => 0.0,
|
||||
'currency' => 'CNY',
|
||||
'status' => AKSRT_Order::STATUS_PENDING,
|
||||
'payment_method' => 'free',
|
||||
'note' => is_string($note) ? $note : '',
|
||||
'expires_at' => gmdate('Y-m-d H:i:s', time() + 30 * MINUTE_IN_SECONDS),
|
||||
]);
|
||||
|
||||
if ($order_number === '') {
|
||||
$this->mark_unlocked($user_id, $topic_id);
|
||||
return [
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'order_number' => '',
|
||||
'amount' => 0,
|
||||
'free' => true,
|
||||
'message' => $message,
|
||||
'reload' => true,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
$paid = AKSRT_Order::get_instance()->update_status($order_number, AKSRT_Order::STATUS_PAID, [
|
||||
'payment_method' => 'free',
|
||||
'payment_transaction_id' => 'free_' . $order_number,
|
||||
'note' => $paid_note,
|
||||
]);
|
||||
if (!$paid) {
|
||||
return new WP_Error('aksrt_order_confirm_failed', __('订单确认失败。', 'aksrt'), ['status' => 500]);
|
||||
}
|
||||
|
||||
$this->mark_unlocked($user_id, $topic_id);
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'order_number' => $order_number,
|
||||
'amount' => 0,
|
||||
'pay_modo' => '0',
|
||||
'free' => true,
|
||||
'message' => $message,
|
||||
'reload' => true,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
private function find_pending_topic_order(int $user_id, int $topic_id): ?array {
|
||||
if ($user_id <= 0 || $topic_id <= 0 || !class_exists('AKSRT_Order_Repository')) {
|
||||
return null;
|
||||
}
|
||||
$orders = AKSRT_Order_Repository::get_instance()->get_user_orders($user_id, [
|
||||
'product_type' => AKSRT_Domain_Model::ORDER_PRODUCT_RESOURCE,
|
||||
'status' => AKSRT_Domain_Model::ORDER_STATUS_PENDING,
|
||||
'limit' => 20,
|
||||
'offset' => 0,
|
||||
]);
|
||||
foreach ($orders as $order) {
|
||||
if ((int) ($order['product_id'] ?? 0) !== $topic_id) {
|
||||
continue;
|
||||
}
|
||||
$meta = $this->decode_order_note_meta($order);
|
||||
if (!empty($meta['topic_pay']) || $this->is_forum_topic_id($topic_id)) {
|
||||
return $order;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private function is_forum_topic_id(int $topic_id): bool {
|
||||
$post = get_post($topic_id);
|
||||
return $post instanceof WP_Post && $post->post_type === 'aksrt_forum_topic';
|
||||
}
|
||||
|
||||
private function decode_order_note_meta(array $order): array {
|
||||
if (isset($order['meta_payload']) && is_array($order['meta_payload']) && $order['meta_payload'] !== []) {
|
||||
return $order['meta_payload'];
|
||||
}
|
||||
$note = (string) ($order['note'] ?? '');
|
||||
if ($note === '') {
|
||||
return [];
|
||||
}
|
||||
$decoded = json_decode($note, true);
|
||||
return is_array($decoded) ? $decoded : [];
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user