275 lines
14 KiB
PHP
275 lines
14 KiB
PHP
<?php
|
|
/**
|
|
* @package AKSRT
|
|
*/
|
|
|
|
namespace AKSRT\Admin\Native;
|
|
|
|
defined('ABSPATH') || exit;
|
|
|
|
class PayIncome {
|
|
|
|
public const CAPABILITY = 'manage_options';
|
|
public const PAGE_SLUG = 'aksrt_income';
|
|
|
|
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 {
|
|
if (!is_admin()) {
|
|
return;
|
|
}
|
|
add_action('admin_post_aksrt_pay_settle_income', [$this, 'handle_settle_income']);
|
|
add_action('admin_post_aksrt_pay_batch_settle_income', [$this, 'handle_batch_settle_income']);
|
|
}
|
|
|
|
public function render_income_page(): void {
|
|
$this->ensure_cap();
|
|
$this->render_header(__('创作分成', 'aksrt'));
|
|
|
|
if (!class_exists('\AKSRT\Payment\Revenue')) {
|
|
echo '<div class="notice notice-error"><p>' . esc_html__('分成模块未初始化。', 'aksrt') . '</p></div>';
|
|
$this->render_footer();
|
|
return;
|
|
}
|
|
|
|
$status = isset($_GET['status']) ? sanitize_key((string) wp_unslash($_GET['status'])) : '';
|
|
$order_status = isset($_GET['order_status']) ? sanitize_key((string) wp_unslash($_GET['order_status'])) : '';
|
|
$user_id = isset($_GET['user_id']) ? max(0, (int) $_GET['user_id']) : 0;
|
|
$post_id = isset($_GET['post_id']) ? max(0, (int) $_GET['post_id']) : 0;
|
|
$currency = isset($_GET['currency']) ? strtolower(sanitize_key((string) wp_unslash($_GET['currency']))) : '';
|
|
if ($currency === 'cny') {
|
|
$currency = 'CNY';
|
|
}
|
|
$order_number = isset($_GET['order_number']) ? sanitize_text_field((string) wp_unslash($_GET['order_number'])) : '';
|
|
$date_from = isset($_GET['date_from']) ? sanitize_text_field((string) wp_unslash($_GET['date_from'])) : '';
|
|
$date_to = isset($_GET['date_to']) ? sanitize_text_field((string) wp_unslash($_GET['date_to'])) : '';
|
|
$page_num = max(1, (int) ($_GET['paged'] ?? 1));
|
|
$per_page = 20;
|
|
$order_statuses = class_exists('\\AKSRT\\Payment\\Order')
|
|
? \AKSRT\Payment\Order::get_valid_statuses()
|
|
: [];
|
|
$svc = \AKSRT\Payment\Revenue::get_instance();
|
|
$stats = $user_id > 0 ? $svc->get_stats($user_id) : $svc->get_stats();
|
|
|
|
$cash_stats = (array) ($stats['cash'] ?? []);
|
|
$points_stats = (array) ($stats['points'] ?? []);
|
|
$cash_amount = static function ($value): string {
|
|
try {
|
|
return \AKSRT\Ledger\AssetAmount::balance(is_string($value) ? $value : '')->formatted();
|
|
} catch (\InvalidArgumentException $e) {
|
|
return '0.00';
|
|
}
|
|
};
|
|
echo '<p>' . esc_html(sprintf(
|
|
__('现金:合计 ¥%1$s · 待结算 ¥%2$s · 已结算 ¥%3$s;积分:合计 %4$s · 待结算 %5$s · 已结算 %6$s', 'aksrt'),
|
|
$cash_amount($cash_stats['total'] ?? '0'),
|
|
$cash_amount($cash_stats['pending'] ?? '0'),
|
|
$cash_amount($cash_stats['settled'] ?? '0'),
|
|
number_format_i18n(max(0, (int) ($points_stats['total'] ?? 0))),
|
|
number_format_i18n(max(0, (int) ($points_stats['pending'] ?? 0))),
|
|
number_format_i18n(max(0, (int) ($points_stats['settled'] ?? 0)))
|
|
)) . '</p>';
|
|
|
|
echo '<form id="aksrt-pay-batch-settle-income" method="post" action="' . esc_url(admin_url('admin-post.php')) . '" style="margin:12px 0;">';
|
|
echo '<input type="hidden" name="action" value="aksrt_pay_batch_settle_income" />';
|
|
echo '<input type="hidden" name="operation_key" value="' . esc_attr(wp_generate_uuid4()) . '" />';
|
|
wp_nonce_field('aksrt_pay_batch_settle_income');
|
|
submit_button(__('批量结算选中记录', 'aksrt'), 'secondary', '', false);
|
|
echo '</form>';
|
|
|
|
echo '<form method="get" style="margin:12px 0;">';
|
|
echo '<input type="hidden" name="page" value="aksrt_income" />';
|
|
echo '<select name="status">';
|
|
foreach (['' => __('全部状态', 'aksrt'), 'pending' => __('待结算', 'aksrt'), 'settled' => __('已结算', 'aksrt')] as $key => $label) {
|
|
printf('<option value="%s"%s>%s</option>', esc_attr($key), selected($status, $key, false), esc_html($label));
|
|
}
|
|
echo '</select> ';
|
|
echo '<select name="order_status"><option value="">' . esc_html__('全部订单状态', 'aksrt') . '</option>';
|
|
foreach ((array) $order_statuses as $key => $label) {
|
|
printf('<option value="%s"%s>%s</option>', esc_attr($key), selected($order_status, $key, false), esc_html($label));
|
|
}
|
|
echo '</select> ';
|
|
echo '<input type="number" min="0" name="user_id" value="' . esc_attr($user_id > 0 ? (string) $user_id : '') . '" placeholder="' . esc_attr__('用户 ID', 'aksrt') . '" class="small-text" /> ';
|
|
echo '<input type="number" min="0" name="post_id" value="' . esc_attr($post_id > 0 ? (string) $post_id : '') . '" placeholder="' . esc_attr__('文章 ID', 'aksrt') . '" class="small-text" /> ';
|
|
echo '<select name="currency"><option value="">' . esc_html__('全部货币', 'aksrt') . '</option><option value="CNY"' . selected($currency, 'CNY', false) . '>CNY</option><option value="points"' . selected($currency, 'points', false) . '>' . esc_html__('积分', 'aksrt') . '</option></select> ';
|
|
echo '<input type="search" name="order_number" value="' . esc_attr($order_number) . '" placeholder="' . esc_attr__('订单号', 'aksrt') . '" /> ';
|
|
echo '<input type="date" name="date_from" value="' . esc_attr($date_from) . '" /> — <input type="date" name="date_to" value="' . esc_attr($date_to) . '" /> ';
|
|
submit_button(__('筛选', 'aksrt'), 'secondary', '', false);
|
|
if ($user_id > 0) {
|
|
echo ' <a class="button button-small" href="' . esc_url($this->page_url('aksrt_income')) . '">' . esc_html__('清除用户过滤', 'aksrt') . '</a>';
|
|
}
|
|
echo '</form>';
|
|
|
|
$list_args = [
|
|
'status' => $status,
|
|
'order_status' => $order_status,
|
|
'limit' => $per_page,
|
|
'offset' => ($page_num - 1) * $per_page,
|
|
'post_id' => $post_id,
|
|
'currency' => $currency,
|
|
'order_number' => $order_number,
|
|
'date_from' => $date_from,
|
|
'date_to' => $date_to,
|
|
];
|
|
if ($user_id > 0) {
|
|
$list_args['user_id'] = $user_id;
|
|
}
|
|
$result = $svc->get_list($list_args);
|
|
$items = $result['items'] ?? [];
|
|
$total = (int) ($result['total'] ?? 0);
|
|
$pages = max(1, (int) ($result['max_pages'] ?? 1));
|
|
|
|
echo '<table class="wp-list-table widefat fixed striped"><thead><tr>';
|
|
echo '<th><input type="checkbox" form="aksrt-pay-batch-settle-income" aria-label="' . esc_attr__('全选待结算记录', 'aksrt') . '" /></th><th>ID</th><th>' . esc_html__('用户', 'aksrt') . '</th><th>' . esc_html__('订单号', 'aksrt') . '</th><th>' . esc_html__('金额', 'aksrt') . '</th><th>' . esc_html__('状态', 'aksrt') . '</th><th>' . esc_html__('时间', 'aksrt') . '</th><th>' . esc_html__('操作', 'aksrt') . '</th>';
|
|
echo '</tr></thead><tbody>';
|
|
if (!$items) {
|
|
echo '<tr><td colspan="8">' . esc_html__('暂无分成记录', 'aksrt') . '</td></tr>';
|
|
}
|
|
foreach ($items as $row) {
|
|
$id = (int) ($row['id'] ?? 0);
|
|
$user_id = (int) ($row['author_id'] ?? 0);
|
|
$user = $user_id > 0 ? get_userdata($user_id) : null;
|
|
$st = (string) ($row['status'] ?? '');
|
|
echo '<tr>';
|
|
echo '<td>' . ($st === 'pending' ? '<input type="checkbox" form="aksrt-pay-batch-settle-income" name="revenue_ids[]" value="' . esc_attr((string) $id) . '" />' : '') . '</td>';
|
|
echo '<td>' . esc_html((string) $id) . '</td><td>' . ($user ? esc_html($user->display_name) : esc_html((string) $user_id)) . '</td>';
|
|
echo '<td><code>' . esc_html((string) ($row['order_number'] ?? '')) . '</code></td>';
|
|
$is_points = (string) ($row['currency'] ?? 'CNY') === 'points';
|
|
$row_currency = $is_points ? __('积分', 'aksrt') : '¥';
|
|
$row_amount = $is_points
|
|
? number_format_i18n(max(0, (int) ($row['amount'] ?? 0)))
|
|
: $cash_amount($row['amount'] ?? '0');
|
|
echo '<td>' . esc_html($row_amount . ' ' . $row_currency) . '</td>';
|
|
echo '<td>' . esc_html($st) . '</td>';
|
|
echo '<td>' . esc_html((string) ($row['created_at'] ?? '')) . '</td>';
|
|
echo '<td>';
|
|
if ($st === 'pending') {
|
|
echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '">';
|
|
echo '<input type="hidden" name="action" value="aksrt_pay_settle_income" />';
|
|
echo '<input type="hidden" name="revenue_id" value="' . esc_attr((string) $id) . '" />';
|
|
echo '<input type="hidden" name="operation_key" value="' . esc_attr(wp_generate_uuid4()) . '" />';
|
|
wp_nonce_field('aksrt_pay_settle_income_' . $id);
|
|
submit_button(__('结算', 'aksrt'), 'small', '', false);
|
|
echo '</form>';
|
|
} else {
|
|
echo '—';
|
|
}
|
|
echo '</td></tr>';
|
|
}
|
|
echo '</tbody></table>';
|
|
$this->render_pagination($pages, $page_num);
|
|
$this->render_footer();
|
|
}
|
|
|
|
public function handle_settle_income(): void {
|
|
if (!current_user_can(self::CAPABILITY)) {
|
|
wp_die(esc_html__('权限不足', 'aksrt'));
|
|
}
|
|
$id = isset($_POST['revenue_id']) ? (int) $_POST['revenue_id'] : 0;
|
|
check_admin_referer('aksrt_pay_settle_income_' . $id);
|
|
$request_key = sanitize_text_field((string) wp_unslash($_POST['operation_key'] ?? ''));
|
|
$operation_key = wp_is_uuid($request_key)
|
|
? \AKSRT\Ledger\OperationKey::deterministic('admin-revenue-settle', [get_current_user_id(), strtolower($request_key)])
|
|
: '';
|
|
$ok = $id > 0 && $operation_key !== '' && class_exists('\AKSRT\Payment\Revenue') && \AKSRT\Payment\Revenue::get_instance()->settle($id, get_current_user_id(), $operation_key);
|
|
wp_safe_redirect($this->page_url('aksrt_income', ['aksrt_pay_msg' => $ok ? 'income_settled' : 'income_failed']));
|
|
exit;
|
|
}
|
|
|
|
|
|
|
|
public function handle_batch_settle_income(): void {
|
|
if (!current_user_can(self::CAPABILITY)) {
|
|
wp_die(esc_html__('权限不足', 'aksrt'));
|
|
}
|
|
check_admin_referer('aksrt_pay_batch_settle_income');
|
|
|
|
$raw_ids = isset($_POST['revenue_ids']) ? wp_unslash($_POST['revenue_ids']) : [];
|
|
$ids = [];
|
|
if (is_array($raw_ids)) {
|
|
foreach ($raw_ids as $raw_id) {
|
|
$id = (int) $raw_id;
|
|
if ($id > 0) {
|
|
$ids[] = $id;
|
|
}
|
|
}
|
|
}
|
|
$ids = array_values(array_unique($ids));
|
|
$request_key = sanitize_text_field((string) wp_unslash($_POST['operation_key'] ?? ''));
|
|
$operation_key = wp_is_uuid($request_key)
|
|
? \AKSRT\Ledger\OperationKey::deterministic('admin-revenue-settle-batch', [get_current_user_id(), strtolower($request_key)])
|
|
: '';
|
|
$batch_result = !empty($ids) && $operation_key !== '' && class_exists('\\AKSRT\\Payment\\Revenue')
|
|
? \AKSRT\Payment\Revenue::get_instance()->settle_batch($ids, get_current_user_id(), $operation_key)
|
|
: ['settled' => [], 'failed' => $ids];
|
|
$message = empty($batch_result['settled'])
|
|
? 'income_failed'
|
|
: (!empty($batch_result['failed']) ? 'income_batch_partial' : 'income_batch_settled');
|
|
wp_safe_redirect($this->page_url('aksrt_income', ['aksrt_pay_msg' => $message]));
|
|
exit;
|
|
}
|
|
|
|
private function ensure_cap(): void {
|
|
if (!current_user_can(self::CAPABILITY)) {
|
|
wp_die(esc_html__('您没有权限访问此页面。', 'aksrt'));
|
|
}
|
|
}
|
|
|
|
private function render_header(string $title): void {
|
|
echo '<div class="wrap">';
|
|
echo '<h1>' . esc_html($title) . '</h1>';
|
|
$this->render_flash();
|
|
}
|
|
|
|
private function render_footer(): void {
|
|
echo '</div>';
|
|
}
|
|
|
|
private function render_flash(): void {
|
|
if (empty($_GET['aksrt_pay_msg'])) {
|
|
return;
|
|
}
|
|
$code = sanitize_key((string) wp_unslash($_GET['aksrt_pay_msg']));
|
|
$map = [
|
|
'income_settled' => __('分成已结算。', 'aksrt'),
|
|
'income_batch_settled' => __('选中分成已批量结算。', 'aksrt'),
|
|
'income_batch_partial' => __('部分分成已结算,失败记录可稍后重试。', 'aksrt'),
|
|
'income_failed' => __('分成结算失败。', 'aksrt'),
|
|
];
|
|
if (!isset($map[$code])) {
|
|
return;
|
|
}
|
|
$class = $code === 'income_batch_partial'
|
|
? 'notice-warning'
|
|
: (str_contains($code, 'failed') ? 'notice-error' : 'notice-success');
|
|
echo '<div class="notice ' . esc_attr($class) . ' is-dismissible"><p>' . esc_html($map[$code]) . '</p></div>';
|
|
}
|
|
|
|
private function page_url(string $slug, array $args = []): string {
|
|
return add_query_arg(array_merge(['page' => $slug], $args), admin_url('admin.php'));
|
|
}
|
|
|
|
private function render_pagination(int $pages, int $page_num): void {
|
|
if ($pages <= 1) {
|
|
return;
|
|
}
|
|
echo '<div class="tablenav"><div class="tablenav-pages">';
|
|
echo paginate_links([
|
|
'base' => add_query_arg('paged', '%#%'),
|
|
'format' => '',
|
|
'prev_text' => '«',
|
|
'next_text' => '»',
|
|
'total' => $pages,
|
|
'current' => $page_num,
|
|
]);
|
|
echo '</div></div>';
|
|
}
|
|
}
|