feat(payment): complete creator income center
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -26,6 +26,7 @@ class PayIncome {
|
||||
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 {
|
||||
@@ -40,6 +41,11 @@ class PayIncome {
|
||||
|
||||
$status = isset($_GET['status']) ? sanitize_key((string) wp_unslash($_GET['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']) ? sanitize_key((string) wp_unslash($_GET['currency'])) : '';
|
||||
$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;
|
||||
$svc = \AKSRT\Payment\Revenue::get_instance();
|
||||
@@ -53,14 +59,24 @@ class PayIncome {
|
||||
number_format((float) ($stats['settled'] ?? 0), 2)
|
||||
)) . '</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" />';
|
||||
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) {
|
||||
foreach (['' => __('全部状态', 'aksrt'), 'pending' => __('待结算', 'aksrt'), 'settled' => __('已结算', 'aksrt'), 'cancelled' => __('已取消', 'aksrt'), 'reversed' => __('已冲正', 'aksrt'), 'pending_recovery' => __('待追缴', 'aksrt')] as $key => $label) {
|
||||
printf('<option value="%s"%s>%s</option>', esc_attr($key), selected($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>';
|
||||
@@ -71,6 +87,11 @@ class PayIncome {
|
||||
'status' => $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;
|
||||
@@ -81,10 +102,10 @@ class PayIncome {
|
||||
$pages = max(1, (int) ($result['max_pages'] ?? 1));
|
||||
|
||||
echo '<table class="wp-list-table widefat fixed striped"><thead><tr>';
|
||||
echo '<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 '<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="7">' . esc_html__('暂无分成记录', 'aksrt') . '</td></tr>';
|
||||
echo '<tr><td colspan="8">' . esc_html__('暂无分成记录', 'aksrt') . '</td></tr>';
|
||||
}
|
||||
foreach ($items as $row) {
|
||||
$id = (int) ($row['id'] ?? 0);
|
||||
@@ -92,8 +113,8 @@ class PayIncome {
|
||||
$user = $user_id > 0 ? get_userdata($user_id) : null;
|
||||
$st = (string) ($row['status'] ?? '');
|
||||
echo '<tr>';
|
||||
echo '<td>' . esc_html((string) $id) . '</td>';
|
||||
echo '<td>' . ($user ? esc_html($user->display_name) : esc_html((string) $user_id)) . '</td>';
|
||||
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>';
|
||||
echo '<td>' . esc_html(number_format((float) ($row['amount'] ?? 0), 2)) . '</td>';
|
||||
echo '<td>' . esc_html($st) . '</td>';
|
||||
@@ -122,13 +143,37 @@ class PayIncome {
|
||||
}
|
||||
$id = isset($_POST['revenue_id']) ? (int) $_POST['revenue_id'] : 0;
|
||||
check_admin_referer('aksrt_pay_settle_income_' . $id);
|
||||
$ok = $id > 0 && class_exists('\AKSRT\Payment\Revenue') && \AKSRT\Payment\Revenue::get_instance()->settle($id);
|
||||
$ok = $id > 0 && class_exists('\AKSRT\Payment\Revenue') && \AKSRT\Payment\Revenue::get_instance()->settle($id, get_current_user_id());
|
||||
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));
|
||||
$ok = !empty($ids)
|
||||
&& class_exists('\\AKSRT\\Payment\\Revenue')
|
||||
&& \AKSRT\Payment\Revenue::get_instance()->settle($ids, get_current_user_id());
|
||||
wp_safe_redirect($this->page_url('aksrt_income', ['aksrt_pay_msg' => $ok ? 'income_batch_settled' : 'income_failed']));
|
||||
exit;
|
||||
}
|
||||
|
||||
private function ensure_cap(): void {
|
||||
if (!current_user_can(self::CAPABILITY)) {
|
||||
wp_die(esc_html__('您没有权限访问此页面。', 'aksrt'));
|
||||
@@ -152,6 +197,7 @@ class PayIncome {
|
||||
$code = sanitize_key((string) wp_unslash($_GET['aksrt_pay_msg']));
|
||||
$map = [
|
||||
'income_settled' => __('分成已结算。', 'aksrt'),
|
||||
'income_batch_settled' => __('选中分成已批量结算。', 'aksrt'),
|
||||
'income_failed' => __('分成结算失败。', 'aksrt'),
|
||||
];
|
||||
if (!isset($map[$code])) {
|
||||
|
||||
@@ -38,7 +38,22 @@ class PaymentController extends ControllerBase {
|
||||
],
|
||||
]);
|
||||
|
||||
// Withdrawal list — unified pagination: page + per_page
|
||||
register_rest_route(self::API_NAMESPACE, '/payment/creator-income', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'get_creator_income'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
register_rest_route(self::API_NAMESPACE, '/payment/creator-income/(?P<id>\d+)/settle', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'settle_creator_income'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
register_rest_route(self::API_NAMESPACE, '/payment/creator-income/order/(?P<order_id>[a-zA-Z0-9_-]+)/reverse', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'reverse_creator_income'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/payment/withdrawals', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'get_withdrawals'],
|
||||
@@ -224,8 +239,60 @@ class PaymentController extends ControllerBase {
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /payment/withdrawals — 提现列表,统一 page + per_page 分页
|
||||
* GET /payment/creator-income — creator income operations list.
|
||||
*/
|
||||
public function get_creator_income(\WP_REST_Request $request): \WP_REST_Response {
|
||||
if (!class_exists('\AKSRT\Payment\Revenue')) {
|
||||
return $this->error(__('创作分成模块未启用', 'aksrt'), 500);
|
||||
}
|
||||
$page = max(1, (int) $request->get_param('page'));
|
||||
$per_page = max(1, min(100, (int) ($request->get_param('per_page') ?: 20)));
|
||||
$result = \AKSRT\Payment\Revenue::get_instance()->get_list([
|
||||
'limit' => $per_page,
|
||||
'offset' => ($page - 1) * $per_page,
|
||||
'status' => sanitize_key((string) $request->get_param('status')),
|
||||
'user_id' => max(0, (int) $request->get_param('user_id')),
|
||||
'post_id' => max(0, (int) $request->get_param('post_id')),
|
||||
'currency' => sanitize_key((string) $request->get_param('currency')),
|
||||
'order_number' => sanitize_text_field((string) $request->get_param('order_number')),
|
||||
'date_from' => sanitize_text_field((string) $request->get_param('date_from')),
|
||||
'date_to' => sanitize_text_field((string) $request->get_param('date_to')),
|
||||
]);
|
||||
$result['page'] = $page;
|
||||
$result['per_page'] = $per_page;
|
||||
return $this->success(['data' => $result]);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /payment/creator-income/{id}/settle — settle one income record.
|
||||
*/
|
||||
public function settle_creator_income(\WP_REST_Request $request): \WP_REST_Response {
|
||||
if (!class_exists('\AKSRT\Payment\Revenue')) {
|
||||
return $this->error(__('创作分成模块未启用', 'aksrt'), 500);
|
||||
}
|
||||
$id = (int) $request->get_param('id');
|
||||
if ($id <= 0 || !\AKSRT\Payment\Revenue::get_instance()->settle($id, get_current_user_id())) {
|
||||
return $this->error(__('结算失败', 'aksrt'));
|
||||
}
|
||||
return $this->success(['message' => __('创作分成已结算', 'aksrt')]);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /payment/creator-income/order/{order_id}/reverse — cancel or reverse order income.
|
||||
*/
|
||||
public function reverse_creator_income(\WP_REST_Request $request): \WP_REST_Response {
|
||||
if (!class_exists('\AKSRT\Payment\Revenue')) {
|
||||
return $this->error(__('创作分成模块未启用', 'aksrt'), 500);
|
||||
}
|
||||
$order_id = sanitize_text_field((string) $request->get_param('order_id'));
|
||||
$params = $this->get_json_params($request);
|
||||
$reason = sanitize_textarea_field((string) ($params['reason'] ?? ''));
|
||||
if ($order_id === '' || !\AKSRT\Payment\Revenue::get_instance()->cancel_or_reverse($order_id, $reason)) {
|
||||
return $this->error(__('冲正失败', 'aksrt'));
|
||||
}
|
||||
return $this->success(['message' => __('创作分成已处理退款/冲正', 'aksrt')]);
|
||||
}
|
||||
|
||||
public function get_withdrawals(\WP_REST_Request $request): \WP_REST_Response {
|
||||
if (!class_exists('\AKSRT\Payment\Withdrawal')) {
|
||||
return $this->success(['data' => ['items' => [], 'total' => 0, 'page' => 1, 'total_pages' => 1, 'stats' => []]]);
|
||||
|
||||
@@ -174,6 +174,8 @@ class SchemaPayment {
|
||||
['key' => 'ratio_vip_2', 'label' => 'VIP2分成比例(%)', 'type' => 'number', 'default' => 0, 'min' => 0, 'max' => 100],
|
||||
['key' => 'payment_revenue_settlement_threshold', 'label' => '最小结算金额', 'type' => 'number', 'default' => 100, 'min' => 1, 'max' => 99999],
|
||||
['key' => 'payment_revenue_cycle_days', 'label' => '结算周期(天)', 'type' => 'number', 'default' => 30, 'min' => 1, 'max' => 365],
|
||||
['key' => 'payment_revenue_settlement_mode', 'label' => '结算模式', 'type' => 'select', 'default' => 'automatic', 'options' => [['value' => 'automatic', 'label' => '自动结算'], ['value' => 'manual', 'label' => '手动结算']]],
|
||||
['key' => 'payment_revenue_settlement_delay_hours', 'label' => '结算延迟(小时)', 'type' => 'number', 'default' => 24, 'min' => 0, 'max' => 8760],
|
||||
['key' => 'payment_revenue_points_rule', 'label' => '积分规则', 'type' => 'textarea', 'default' => ''],
|
||||
['key' => 'payment_revenue_note', 'label' => '结算备注', 'type' => 'textarea', 'default' => ''],
|
||||
]],
|
||||
|
||||
Reference in New Issue
Block a user