826 lines
33 KiB
PHP
826 lines
33 KiB
PHP
<?php
|
|
/**
|
|
* @package AKSRT
|
|
*/
|
|
|
|
namespace AKSRT\Admin;
|
|
|
|
defined('ABSPATH') || exit;
|
|
|
|
class PaymentController extends ControllerBase {
|
|
|
|
private static $instance = null;
|
|
|
|
public static function get_instance(): self {
|
|
if (null === self::$instance) self::$instance = new self();
|
|
return self::$instance;
|
|
}
|
|
|
|
public function register_routes(): void {
|
|
$this->register_section_routes('payment', 'payment');
|
|
|
|
register_rest_route(self::API_NAMESPACE, '/payment/overview', [
|
|
'methods' => 'GET',
|
|
'callback' => [$this, 'rest_payment_overview'],
|
|
'permission_callback' => [$this, 'check_permission'],
|
|
]);
|
|
|
|
// Revenue stats (with optional time-series)
|
|
register_rest_route(self::API_NAMESPACE, '/payment/revenue', [
|
|
'methods' => 'GET',
|
|
'callback' => [$this, 'get_revenue'],
|
|
'permission_callback' => [$this, 'check_permission'],
|
|
'args' => [
|
|
'date_from' => ['default' => '', 'sanitize_callback' => 'sanitize_text_field'],
|
|
'date_to' => ['default' => '', 'sanitize_callback' => 'sanitize_text_field'],
|
|
'granularity' => ['default' => 'day', 'sanitize_callback' => 'sanitize_text_field'],
|
|
'time_series' => ['default' => false, 'sanitize_callback' => 'rest_sanitize_boolean'],
|
|
],
|
|
]);
|
|
|
|
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/withdrawals', [
|
|
'methods' => 'GET',
|
|
'callback' => [$this, 'get_withdrawals'],
|
|
'permission_callback' => [$this, 'check_permission'],
|
|
]);
|
|
|
|
// Withdrawal status update
|
|
register_rest_route(self::API_NAMESPACE, '/payment/withdrawals/(?P<id>\d+)', [
|
|
'methods' => 'POST',
|
|
'callback' => [$this, 'update_withdrawal_status'],
|
|
'permission_callback' => [$this, 'check_permission'],
|
|
]);
|
|
|
|
// ── Charge Card ──
|
|
|
|
// Charge card list
|
|
register_rest_route(self::API_NAMESPACE, '/payment/charge-cards', [
|
|
'methods' => 'GET',
|
|
'callback' => [$this, 'get_charge_cards'],
|
|
'permission_callback' => [$this, 'check_permission'],
|
|
]);
|
|
register_rest_route(self::API_NAMESPACE, '/payment/charge-cards/(?P<id>\d+)', [
|
|
'methods' => 'GET',
|
|
'callback' => [$this, 'get_charge_card_detail'],
|
|
'permission_callback' => [$this, 'check_permission'],
|
|
]);
|
|
|
|
// Charge card create
|
|
register_rest_route(self::API_NAMESPACE, '/payment/charge-cards', [
|
|
'methods' => 'POST',
|
|
'callback' => [$this, 'create_charge_card'],
|
|
'permission_callback' => [$this, 'check_permission'],
|
|
]);
|
|
|
|
// Charge card batch create
|
|
register_rest_route(self::API_NAMESPACE, '/payment/charge-cards/batch', [
|
|
'methods' => 'POST',
|
|
'callback' => [$this, 'batch_create_charge_cards'],
|
|
'permission_callback' => [$this, 'check_permission'],
|
|
]);
|
|
|
|
// Charge card disable
|
|
register_rest_route(self::API_NAMESPACE, '/payment/charge-cards/(?P<id>\d+)/disable', [
|
|
'methods' => 'POST',
|
|
'callback' => [$this, 'disable_charge_card'],
|
|
'permission_callback' => [$this, 'check_permission'],
|
|
]);
|
|
|
|
}
|
|
|
|
public function rest_payment_overview(\WP_REST_Request $request): \WP_REST_Response {
|
|
if (!class_exists('\AKSRT\Settings\Settings')) {
|
|
return $this->error('设置模块未初始化', 500);
|
|
}
|
|
|
|
$paid = \AKSRT\Settings\Settings::get_paid_content_settings();
|
|
$recharge = \AKSRT\Settings\Settings::get_payment_recharge_settings();
|
|
$vip = \AKSRT\Settings\Settings::get_payment_vip_settings();
|
|
$revenue = \AKSRT\Settings\Settings::get_payment_revenue_settings();
|
|
$affiliate = \AKSRT\Settings\Settings::get_payment_affiliate_settings();
|
|
$gateway = \AKSRT\Settings\Settings::get_payment_gateway_settings();
|
|
|
|
return $this->success([
|
|
'data' => [
|
|
'paid' => $paid,
|
|
'recharge' => $recharge,
|
|
'vip' => $vip,
|
|
'revenue' => $revenue,
|
|
'affiliate' => $affiliate,
|
|
'gateway' => $gateway,
|
|
'currency_name' => \AKSRT\Settings\Settings::get_currency_name(),
|
|
],
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* GET /payment/revenue — revenue stats with optional time-series
|
|
*/
|
|
public function get_revenue(\WP_REST_Request $request): \WP_REST_Response {
|
|
$revenue_stats = [
|
|
'cash' => ['total' => 0, 'pending' => 0, 'settled' => 0, 'count' => 0],
|
|
'points' => ['total' => 0, 'pending' => 0, 'settled' => 0, 'count' => 0],
|
|
];
|
|
if (class_exists('\AKSRT\Payment\Revenue')) {
|
|
$revenue_stats = \AKSRT\Payment\Revenue::get_instance()->get_stats();
|
|
}
|
|
|
|
$withdrawal_stats = ['total' => 0, 'pending' => 0, 'processing' => 0, 'completed' => 0, 'rejected' => 0, 'total_paid' => 0];
|
|
if (class_exists('\AKSRT\Payment\Withdrawal')) {
|
|
$withdrawal_stats = \AKSRT\Payment\Withdrawal::get_instance()->get_stats();
|
|
}
|
|
|
|
$response = [
|
|
'revenue' => $revenue_stats,
|
|
'withdrawal' => $withdrawal_stats,
|
|
];
|
|
|
|
// Time-series data
|
|
if ($request->get_param('time_series')) {
|
|
$response['time_series'] = $this->get_revenue_time_series($request);
|
|
}
|
|
|
|
return $this->success(['data' => $response]);
|
|
}
|
|
|
|
/**
|
|
* Build revenue time-series data.
|
|
*/
|
|
private function get_revenue_time_series(\WP_REST_Request $request): array {
|
|
global $wpdb;
|
|
$prefix = $wpdb->prefix;
|
|
|
|
$date_from = sanitize_text_field((string) $request->get_param('date_from'));
|
|
$date_to = sanitize_text_field((string) $request->get_param('date_to'));
|
|
$granularity = sanitize_text_field((string) $request->get_param('granularity'));
|
|
|
|
// Default: last 30 days in the WordPress site timezone.
|
|
$timezone = function_exists('wp_timezone') ? wp_timezone() : new \DateTimeZone('UTC');
|
|
$now = new \DateTimeImmutable('now', $timezone);
|
|
if (!$date_from) {
|
|
$date_from = $now->modify('-30 days')->format('Y-m-d');
|
|
}
|
|
if (!$date_to) {
|
|
$date_to = $now->format('Y-m-d');
|
|
}
|
|
|
|
// Granularity: day, week, month
|
|
$date_format = '%Y-%m-%d';
|
|
if ($granularity === 'week') {
|
|
$date_format = '%x-W%v';
|
|
} elseif ($granularity === 'month') {
|
|
$date_format = '%Y-%m';
|
|
}
|
|
|
|
$table = $prefix . 'aksrt_revenue';
|
|
$results = $wpdb->get_results($wpdb->prepare(
|
|
"SELECT currency, DATE_FORMAT(created_at, %s) AS period,
|
|
SUM(amount) AS total_amount,
|
|
COUNT(*) AS order_count
|
|
FROM {$table}
|
|
WHERE status IN ('pending', 'settled')
|
|
AND created_at >= %s AND created_at < DATE_ADD(%s, INTERVAL 1 DAY)
|
|
GROUP BY currency, period
|
|
ORDER BY period ASC",
|
|
$date_format,
|
|
$date_from,
|
|
$date_to
|
|
));
|
|
|
|
$series = ['cash' => [], 'points' => []];
|
|
foreach ((array) $results as $row) {
|
|
$key = (string) ($row->currency ?? '') === 'points' ? 'points' : 'cash';
|
|
$series[$key][] = [
|
|
'date' => (string) $row->period,
|
|
'amount' => (float) $row->total_amount,
|
|
'count' => (int) $row->order_count,
|
|
];
|
|
}
|
|
|
|
return $series;
|
|
}
|
|
|
|
/**
|
|
* 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_status' => sanitize_key((string) $request->get_param('order_status')),
|
|
'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')]);
|
|
}
|
|
|
|
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' => []]]);
|
|
}
|
|
|
|
$status = sanitize_key((string) $request->get_param('status'));
|
|
$page = max(1, (int) $request->get_param('page'));
|
|
$per_page = max(1, min(50, (int) $request->get_param('per_page'))) ?: 20;
|
|
|
|
$items = \AKSRT\Payment\Withdrawal::get_instance()->get_all([
|
|
'status' => $status,
|
|
'limit' => $per_page,
|
|
'offset' => ($page - 1) * $per_page,
|
|
]);
|
|
|
|
$total = \AKSRT\Payment\Withdrawal::get_instance()->get_count(['status' => $status]);
|
|
$stats = \AKSRT\Payment\Withdrawal::get_instance()->get_stats();
|
|
|
|
return $this->success([
|
|
'data' => [
|
|
'items' => $items,
|
|
'total' => $total,
|
|
'page' => $page,
|
|
'total_pages' => max(1, (int) ceil($total / $per_page)),
|
|
'stats' => $stats,
|
|
],
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* POST /payment/withdrawals/{id} — 更新提现状态
|
|
*/
|
|
public function update_withdrawal_status(\WP_REST_Request $request): \WP_REST_Response {
|
|
if (!class_exists('\AKSRT\Payment\Withdrawal')) {
|
|
return $this->error('提现模块未初始化', 500);
|
|
}
|
|
|
|
$id = (int) $request->get_param('id');
|
|
$params = $this->get_json_params($request);
|
|
$new_status = sanitize_key((string) ($params['status'] ?? ''));
|
|
$note = sanitize_textarea_field((string) ($params['note'] ?? ''));
|
|
|
|
$valid_statuses = ['pending', 'processing', 'completed', 'rejected'];
|
|
if (!in_array($new_status, $valid_statuses, true)) {
|
|
return $this->error('无效的提现状态');
|
|
}
|
|
|
|
if ($id <= 0) {
|
|
return $this->error('无效的提现 ID');
|
|
}
|
|
|
|
$result = \AKSRT\Payment\Withdrawal::get_instance()->process_status_change($id, $new_status, $note);
|
|
|
|
if (is_wp_error($result)) {
|
|
return $this->error($result->get_error_message());
|
|
}
|
|
if ($result) {
|
|
return $this->success(['message' => __('提现状态已更新', 'aksrt')]);
|
|
}
|
|
return $this->error(__('更新失败', 'aksrt'));
|
|
}
|
|
|
|
/* ------------------------------------------------------------------
|
|
* Charge Card
|
|
* ----------------------------------------------------------------*/
|
|
|
|
/**
|
|
* GET /payment/charge-cards — 充值卡列表
|
|
*/
|
|
public function get_charge_cards(\WP_REST_Request $request): \WP_REST_Response {
|
|
if (!class_exists('\AKSRT\Payment\ChargeCard')) {
|
|
return $this->success(['data' => ['items' => [], 'total' => 0, 'page' => 1, 'max_pages' => 1]]);
|
|
}
|
|
|
|
global $wpdb;
|
|
|
|
$table = \AKSRT\Core\DBMigrator::table('charge_card');
|
|
$page = max(1, (int) ($request->get_param('page') ?: 1));
|
|
$per_page = max(1, min(100, (int) ($request->get_param('per_page') ?: 20)));
|
|
$status = sanitize_key((string) $request->get_param('status'));
|
|
$keyword = trim(sanitize_text_field((string) $request->get_param('keyword')));
|
|
$date_from = sanitize_text_field((string) $request->get_param('date_from'));
|
|
$date_to = sanitize_text_field((string) $request->get_param('date_to'));
|
|
|
|
$where = [];
|
|
$params = [];
|
|
|
|
if ($status !== '') {
|
|
$where[] = 'status = %s';
|
|
$params[] = $status;
|
|
}
|
|
if ($date_from !== '') {
|
|
$where[] = 'created_at >= %s';
|
|
$params[] = $date_from . ' 00:00:00';
|
|
}
|
|
if ($date_to !== '') {
|
|
$where[] = 'created_at <= %s';
|
|
$params[] = $date_to . ' 23:59:59';
|
|
}
|
|
|
|
$where_sql = $where ? 'WHERE ' . implode(' AND ', $where) : '';
|
|
$sql = "SELECT * FROM {$table} {$where_sql} ORDER BY created_at DESC LIMIT 500";
|
|
$rows = $params ? $wpdb->get_results($wpdb->prepare($sql, $params), ARRAY_A) : $wpdb->get_results($sql, ARRAY_A);
|
|
|
|
$items = [];
|
|
foreach ((array) $rows as $row) {
|
|
$item = $this->build_charge_card_admin_item($row);
|
|
if ($keyword !== '' && mb_stripos(implode(' ', array_filter([
|
|
(string) ($item['code'] ?? ''),
|
|
(string) ($item['password'] ?? ''),
|
|
(string) ($item['card'] ?? ''),
|
|
(string) ($item['type_label'] ?? ''),
|
|
(string) ($item['note'] ?? ''),
|
|
(string) ($item['used_by_user']['display_name'] ?? ''),
|
|
(string) ($item['used_by_user']['email'] ?? ''),
|
|
])), $keyword) === false) {
|
|
continue;
|
|
}
|
|
$items[] = $item;
|
|
}
|
|
|
|
$total = count($items);
|
|
$offset = ($page - 1) * $per_page;
|
|
$stats = \AKSRT\Payment\ChargeCard::get_instance()->get_stats();
|
|
|
|
return $this->success([
|
|
'data' => [
|
|
'items' => array_values(array_slice($items, $offset, $per_page)),
|
|
'total' => $total,
|
|
'page' => $page,
|
|
'per_page' => $per_page,
|
|
'max_pages' => max(1, (int) ceil(max(1, $total) / $per_page)),
|
|
'stats' => $stats,
|
|
'options' => [
|
|
'statuses' => [
|
|
['label' => __('全部状态', 'aksrt'), 'value' => ''],
|
|
['label' => __('未使用', 'aksrt'), 'value' => 'active'],
|
|
['label' => __('已使用', 'aksrt'), 'value' => 'used'],
|
|
['label' => __('已过期', 'aksrt'), 'value' => 'expired'],
|
|
['label' => __('已禁用', 'aksrt'), 'value' => 'disabled'],
|
|
],
|
|
],
|
|
],
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* POST /payment/charge-cards — 创建充值卡
|
|
*/
|
|
public function create_charge_card(\WP_REST_Request $request): \WP_REST_Response {
|
|
if (!class_exists('\AKSRT\Payment\ChargeCard')) {
|
|
return $this->error('充值卡系统未启用', 500);
|
|
}
|
|
|
|
$params = $this->get_json_params($request);
|
|
$type = sanitize_key((string) ($params['type'] ?? 'balance'));
|
|
if (!in_array($type, ['balance', 'vip', 'credits'], true)) {
|
|
$type = 'balance';
|
|
}
|
|
$password = sanitize_text_field((string) ($params['password'] ?? $params['code'] ?? ''));
|
|
if ($password === '') {
|
|
return $this->error('卡密不能为空');
|
|
}
|
|
|
|
$id = \AKSRT\Payment\ChargeCard::get_instance()->create(array_merge($params, [
|
|
'type' => $type,
|
|
'password' => $password,
|
|
]));
|
|
|
|
if ($id > 0) {
|
|
return $this->success([
|
|
'message' => __('充值卡已创建', 'aksrt'),
|
|
'data' => ['id' => $id],
|
|
], 201);
|
|
}
|
|
|
|
return $this->error(__('创建失败(可能卡密已存在或参数无效)', 'aksrt'));
|
|
}
|
|
|
|
/**
|
|
* POST /payment/charge-cards/batch — 批量生成充值卡
|
|
*/
|
|
public function batch_create_charge_cards(\WP_REST_Request $request): \WP_REST_Response {
|
|
if (!class_exists('\AKSRT\Payment\ChargeCard')) {
|
|
return $this->error('充值卡系统未启用', 500);
|
|
}
|
|
|
|
$params = $this->get_json_params($request);
|
|
$type = sanitize_key((string) ($params['type'] ?? 'balance'));
|
|
if (!in_array($type, ['balance', 'vip', 'credits'], true)) {
|
|
$type = 'balance';
|
|
}
|
|
$count = max(1, min(100, (int) ($params['count'] ?? 1)));
|
|
$length = max(8, min(32, (int) ($params['length'] ?? 16)));
|
|
$prefix = sanitize_text_field((string) ($params['prefix'] ?? ''));
|
|
$note = sanitize_textarea_field((string) ($params['note'] ?? ''));
|
|
$expires = sanitize_text_field((string) ($params['expires_at'] ?? ''));
|
|
|
|
$meta_fields = [
|
|
'amount' => (float) ($params['amount'] ?? 0),
|
|
'level' => (int) ($params['level'] ?? 1),
|
|
'days' => (int) ($params['days'] ?? $params['vip_days'] ?? 30),
|
|
'points' => (int) ($params['points'] ?? $params['credits'] ?? 0),
|
|
];
|
|
|
|
if ($type === 'balance' && $meta_fields['amount'] <= 0) {
|
|
return $this->error('余额卡金额必须大于 0');
|
|
}
|
|
if ($type === 'credits' && $meta_fields['points'] <= 0) {
|
|
return $this->error('积分卡积分必须大于 0');
|
|
}
|
|
|
|
$codes = \AKSRT\Payment\ChargeCard::get_instance()->batch_create(
|
|
$type,
|
|
$meta_fields,
|
|
$count,
|
|
$length,
|
|
$prefix,
|
|
$expires,
|
|
$note
|
|
);
|
|
|
|
return $this->success([
|
|
'message' => sprintf(__('已生成 %d 张充值卡', 'aksrt'), count($codes)),
|
|
'data' => ['codes' => $codes, 'count' => count($codes), 'type' => $type],
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* POST /payment/charge-cards/{id}/disable — 禁用充值卡
|
|
*/
|
|
public function disable_charge_card(\WP_REST_Request $request): \WP_REST_Response {
|
|
if (!class_exists('\AKSRT\Payment\ChargeCard')) {
|
|
return $this->error('充值卡系统未启用', 500);
|
|
}
|
|
|
|
$id = (int) $request->get_param('id');
|
|
if ($id <= 0) {
|
|
return $this->error('参数无效');
|
|
}
|
|
|
|
$success = \AKSRT\Payment\ChargeCard::get_instance()->disable($id);
|
|
|
|
if ($success) {
|
|
return $this->success(['message' => __('充值卡已禁用', 'aksrt')]);
|
|
}
|
|
return $this->error(__('操作失败', 'aksrt'));
|
|
}
|
|
|
|
public function get_charge_card_detail(\WP_REST_Request $request): \WP_REST_Response {
|
|
global $wpdb;
|
|
|
|
$id = (int) $request->get_param('id');
|
|
$table = \AKSRT\Core\DBMigrator::table('charge_card');
|
|
|
|
if ($id <= 0 || !$table) {
|
|
return $this->error(__('充值卡不存在', 'aksrt'), 404);
|
|
}
|
|
|
|
$row = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$table} WHERE id = %d", $id), ARRAY_A);
|
|
if (!$row) {
|
|
return $this->error(__('充值卡不存在', 'aksrt'), 404);
|
|
}
|
|
|
|
return $this->success([
|
|
'data' => [
|
|
'card' => $this->build_charge_card_admin_item($row),
|
|
],
|
|
]);
|
|
}
|
|
|
|
/* ------------------------------------------------------------------
|
|
* Rebate
|
|
* ----------------------------------------------------------------*/
|
|
|
|
/**
|
|
* GET /payment/rebate/log — 返佣记录列表
|
|
*/
|
|
public function get_rebate_list(\WP_REST_Request $request): \WP_REST_Response {
|
|
if (!class_exists('\AKSRT\Payment\Rebate')) {
|
|
return $this->success(['data' => ['items' => [], 'total' => 0, 'page' => 1, 'max_pages' => 1]]);
|
|
}
|
|
|
|
global $wpdb;
|
|
|
|
$table = \AKSRT\Core\DBMigrator::table('rebate_log');
|
|
$page = max(1, (int) ($request->get_param('page') ?: 1));
|
|
$per_page = max(1, min(100, (int) ($request->get_param('per_page') ?: 20)));
|
|
$status = sanitize_key((string) $request->get_param('status'));
|
|
$type = sanitize_key((string) $request->get_param('type'));
|
|
$keyword = trim(sanitize_text_field((string) $request->get_param('keyword')));
|
|
$user_id = (int) $request->get_param('user_id');
|
|
$date_from = sanitize_text_field((string) $request->get_param('date_from'));
|
|
$date_to = sanitize_text_field((string) $request->get_param('date_to'));
|
|
|
|
$where = [];
|
|
$params = [];
|
|
|
|
if ($status !== '') {
|
|
$where[] = 'status = %s';
|
|
$params[] = $status;
|
|
}
|
|
if ($type !== '') {
|
|
$where[] = 'type = %s';
|
|
$params[] = $type;
|
|
}
|
|
if ($user_id > 0) {
|
|
$where[] = 'user_id = %d';
|
|
$params[] = $user_id;
|
|
}
|
|
if ($date_from !== '') {
|
|
$where[] = 'created_at >= %s';
|
|
$params[] = $date_from . ' 00:00:00';
|
|
}
|
|
if ($date_to !== '') {
|
|
$where[] = 'created_at <= %s';
|
|
$params[] = $date_to . ' 23:59:59';
|
|
}
|
|
|
|
$where_sql = $where ? 'WHERE ' . implode(' AND ', $where) : '';
|
|
$sql = "SELECT * FROM {$table} {$where_sql} ORDER BY created_at DESC LIMIT 500";
|
|
$rows = $params ? $wpdb->get_results($wpdb->prepare($sql, $params), ARRAY_A) : $wpdb->get_results($sql, ARRAY_A);
|
|
|
|
$items = [];
|
|
foreach ((array) $rows as $row) {
|
|
$item = $this->build_rebate_admin_item($row);
|
|
if ($keyword !== '' && mb_stripos(implode(' ', array_filter([
|
|
(string) ($item['order_number'] ?? ''),
|
|
(string) ($item['note'] ?? ''),
|
|
(string) ($item['user']['display_name'] ?? ''),
|
|
(string) ($item['source_user']['display_name'] ?? ''),
|
|
])), $keyword) === false) {
|
|
continue;
|
|
}
|
|
$items[] = $item;
|
|
}
|
|
|
|
$total = count($items);
|
|
$offset = ($page - 1) * $per_page;
|
|
$stats = \AKSRT\Payment\Rebate::get_instance()->get_stats();
|
|
|
|
return $this->success([
|
|
'data' => [
|
|
'items' => array_values(array_slice($items, $offset, $per_page)),
|
|
'total' => $total,
|
|
'page' => $page,
|
|
'per_page' => $per_page,
|
|
'max_pages' => max(1, (int) ceil(max(1, $total) / $per_page)),
|
|
'stats' => $stats,
|
|
'options' => [
|
|
'statuses' => [
|
|
['label' => __('全部状态', 'aksrt'), 'value' => ''],
|
|
['label' => __('待结算', 'aksrt'), 'value' => 'pending'],
|
|
['label' => __('已结算', 'aksrt'), 'value' => 'settled'],
|
|
['label' => __('已取消', 'aksrt'), 'value' => 'cancelled'],
|
|
],
|
|
'types' => [
|
|
['label' => __('全部类型', 'aksrt'), 'value' => ''],
|
|
['label' => __('订单返佣', 'aksrt'), 'value' => 'order'],
|
|
['label' => __('邀请返佣', 'aksrt'), 'value' => 'invite'],
|
|
],
|
|
],
|
|
],
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* POST /payment/rebate/{id}/settle — 结算返佣
|
|
*/
|
|
public function settle_rebate(\WP_REST_Request $request): \WP_REST_Response {
|
|
if (!class_exists('\AKSRT\Payment\Rebate')) {
|
|
return $this->error('返佣系统未启用', 500);
|
|
}
|
|
|
|
$id = (int) $request->get_param('id');
|
|
$params = $this->get_json_params($request);
|
|
$note = sanitize_textarea_field((string) ($params['note'] ?? ''));
|
|
if ($id <= 0) {
|
|
return $this->error('参数无效');
|
|
}
|
|
|
|
$success = \AKSRT\Payment\Rebate::get_instance()->settle($id);
|
|
|
|
if ($success) {
|
|
$this->append_rebate_process_log($id, 'settled', $note);
|
|
return $this->success(['message' => __('返佣已结算', 'aksrt')]);
|
|
}
|
|
return $this->error(__('结算失败', 'aksrt'));
|
|
}
|
|
|
|
/**
|
|
* POST /payment/rebate/{id}/cancel — 取消返佣
|
|
*/
|
|
public function cancel_rebate(\WP_REST_Request $request): \WP_REST_Response {
|
|
if (!class_exists('\AKSRT\Payment\Rebate')) {
|
|
return $this->error('返佣系统未启用', 500);
|
|
}
|
|
|
|
$id = (int) $request->get_param('id');
|
|
$params = $this->get_json_params($request);
|
|
$note = sanitize_textarea_field((string) ($params['note'] ?? ''));
|
|
if ($id <= 0) {
|
|
return $this->error('参数无效');
|
|
}
|
|
|
|
$success = \AKSRT\Payment\Rebate::get_instance()->cancel($id);
|
|
|
|
if ($success) {
|
|
$this->append_rebate_process_log($id, 'cancelled', $note);
|
|
return $this->success(['message' => __('返佣已取消', 'aksrt')]);
|
|
}
|
|
return $this->error(__('操作失败', 'aksrt'));
|
|
}
|
|
|
|
public function get_rebate_detail(\WP_REST_Request $request): \WP_REST_Response {
|
|
global $wpdb;
|
|
|
|
$id = (int) $request->get_param('id');
|
|
$table = \AKSRT\Core\DBMigrator::table('rebate_log');
|
|
|
|
if ($id <= 0 || !$table) {
|
|
return $this->error(__('返佣记录不存在', 'aksrt'), 404);
|
|
}
|
|
|
|
$row = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$table} WHERE id = %d", $id), ARRAY_A);
|
|
if (!$row) {
|
|
return $this->error(__('返佣记录不存在', 'aksrt'), 404);
|
|
}
|
|
|
|
return $this->success([
|
|
'data' => [
|
|
'record' => $this->build_rebate_admin_item($row),
|
|
'history' => $this->get_rebate_process_log($id),
|
|
],
|
|
]);
|
|
}
|
|
|
|
private function build_charge_card_admin_item(array $row): array {
|
|
$used_by = (int) ($row['used_by'] ?? 0);
|
|
$user = $used_by > 0 ? get_userdata($used_by) : null;
|
|
$status = sanitize_key((string) ($row['status'] ?? 'active'));
|
|
$type = sanitize_key((string) ($row['type'] ?? 'balance'));
|
|
$statusMap = [
|
|
'active' => __('未使用', 'aksrt'),
|
|
'used' => __('已使用', 'aksrt'),
|
|
'expired' => __('已过期', 'aksrt'),
|
|
'disabled' => __('已禁用', 'aksrt'),
|
|
];
|
|
$typeMap = [
|
|
'balance' => __('余额卡', 'aksrt'),
|
|
'vip' => __('会员卡', 'aksrt'),
|
|
'credits' => __('积分卡', 'aksrt'),
|
|
];
|
|
|
|
$meta = [];
|
|
if (!empty($row['meta'])) {
|
|
if (is_array($row['meta'])) {
|
|
$meta = $row['meta'];
|
|
} else {
|
|
$decoded = json_decode((string) $row['meta'], true);
|
|
if (is_array($decoded)) {
|
|
$meta = $decoded;
|
|
}
|
|
}
|
|
}
|
|
|
|
$password = (string) ($row['password'] ?? $row['code'] ?? '');
|
|
$card_no = (string) ($row['card'] ?? '');
|
|
|
|
return [
|
|
'id' => (int) ($row['id'] ?? 0),
|
|
'code' => $password,
|
|
'password' => $password,
|
|
'card' => $card_no,
|
|
'type' => $type,
|
|
'type_label' => $typeMap[$type] ?? $type,
|
|
'amount' => (float) ($row['amount'] ?? 0),
|
|
'balance' => (float) ($row['balance'] ?? 0),
|
|
'meta' => $meta,
|
|
'level' => (int) ($meta['level'] ?? 0),
|
|
'days' => (int) ($meta['days'] ?? 0),
|
|
'points' => (int) ($meta['points'] ?? 0),
|
|
'order_number' => (string) ($row['order_number'] ?? ''),
|
|
'status' => $status,
|
|
'status_label' => $statusMap[$status] ?? $status,
|
|
'used_by' => $used_by,
|
|
'used_at' => (string) ($row['used_at'] ?? ''),
|
|
'expires_at' => (string) ($row['expires_at'] ?? ''),
|
|
'created_at' => (string) ($row['created_at'] ?? ''),
|
|
'note' => (string) ($row['note'] ?? ''),
|
|
'used_by_user' => $user instanceof \WP_User ? [
|
|
'id' => (int) $user->ID,
|
|
'display_name' => $user->display_name,
|
|
'email' => $user->user_email,
|
|
] : null,
|
|
];
|
|
}
|
|
|
|
private function build_rebate_admin_item(array $row): array {
|
|
$user_id = (int) ($row['user_id'] ?? 0);
|
|
$source_user_id = (int) ($row['source_user_id'] ?? 0);
|
|
$user = $user_id > 0 ? get_userdata($user_id) : null;
|
|
$source_user = $source_user_id > 0 ? get_userdata($source_user_id) : null;
|
|
$status = sanitize_key((string) ($row['status'] ?? 'pending'));
|
|
$type = sanitize_key((string) ($row['type'] ?? 'order'));
|
|
|
|
$statusMap = [
|
|
'pending' => __('待结算', 'aksrt'),
|
|
'settled' => __('已结算', 'aksrt'),
|
|
'cancelled' => __('已取消', 'aksrt'),
|
|
];
|
|
$typeMap = [
|
|
'order' => __('订单返佣', 'aksrt'),
|
|
'invite' => __('邀请返佣', 'aksrt'),
|
|
];
|
|
|
|
return [
|
|
'id' => (int) ($row['id'] ?? 0),
|
|
'order_number' => (string) ($row['order_number'] ?? ''),
|
|
'amount' => (float) ($row['amount'] ?? 0),
|
|
'status' => $status,
|
|
'status_label' => $statusMap[$status] ?? $status,
|
|
'type' => $type,
|
|
'type_label' => $typeMap[$type] ?? $type,
|
|
'created_at' => (string) ($row['created_at'] ?? ''),
|
|
'settled_at' => (string) ($row['settled_at'] ?? ''),
|
|
'note' => (string) ($row['note'] ?? ''),
|
|
'history_count' => count($this->get_rebate_process_log((int) ($row['id'] ?? 0))),
|
|
'user' => $user instanceof \WP_User ? [
|
|
'id' => (int) $user->ID,
|
|
'display_name' => $user->display_name,
|
|
'email' => $user->user_email,
|
|
] : null,
|
|
'source_user' => $source_user instanceof \WP_User ? [
|
|
'id' => (int) $source_user->ID,
|
|
'display_name' => $source_user->display_name,
|
|
'email' => $source_user->user_email,
|
|
] : null,
|
|
];
|
|
}
|
|
|
|
private function append_rebate_process_log(int $rebate_id, string $event, string $note = ''): void {
|
|
$log = get_option('aksrt_rebate_process_log', []);
|
|
if (!is_array($log)) {
|
|
$log = [];
|
|
}
|
|
|
|
$rebate_id = (int) $rebate_id;
|
|
if (!isset($log[$rebate_id]) || !is_array($log[$rebate_id])) {
|
|
$log[$rebate_id] = [];
|
|
}
|
|
|
|
$log[$rebate_id][] = [
|
|
'event' => sanitize_key($event),
|
|
'note' => sanitize_textarea_field($note),
|
|
'operator' => get_current_user_id(),
|
|
'operator_name' => ($user = wp_get_current_user()) ? $user->display_name : '',
|
|
'time' => current_time('mysql'),
|
|
];
|
|
|
|
update_option('aksrt_rebate_process_log', $log, false);
|
|
}
|
|
|
|
private function get_rebate_process_log(int $rebate_id): array {
|
|
$log = get_option('aksrt_rebate_process_log', []);
|
|
if (!is_array($log) || !isset($log[$rebate_id]) || !is_array($log[$rebate_id])) {
|
|
return [];
|
|
}
|
|
|
|
return array_values($log[$rebate_id]);
|
|
}
|
|
}
|