feat(payment): 支付&付费菜单按参考主题重构并新增提现策略页
- 子菜单重组为知识付费/余额充值/VIP会员/创作分成/推广返佣/收款接口/提现策略 - 收款接口页 1:1 复刻 Zibll 双下拉结构,仅保留支付宝/微信/PayPal/Stripe/易支付 - 订单前缀/流水号前缀/提现单号前缀配置保留并移入收款接口页 - 知识付费/推广返佣按参考主题全字段复刻,余额充值并入充值卡配置 - 新增 tabbed/between_number/page_select 表单控件及后端 pages 搜索源 - 清理旧 SPA 组件、无消费者 REST 端点与废弃配置键(不写双路兼容) - 更新存量契约测试至新 schema 文件
This commit is contained in:
@@ -19,49 +19,6 @@ class PaymentController extends ControllerBase {
|
||||
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'],
|
||||
'args' => [
|
||||
'page' => ['type' => 'integer', 'minimum' => 1, 'default' => 1],
|
||||
'per_page' => ['type' => 'integer', 'minimum' => 1, 'maximum' => 100, 'default' => 20],
|
||||
'status' => ['type' => 'string', 'enum' => ['', 'pending', 'settled']],
|
||||
'user_id' => ['type' => 'integer', 'minimum' => 0, 'default' => 0],
|
||||
'post_id' => ['type' => 'integer', 'minimum' => 0, 'default' => 0],
|
||||
'currency' => ['type' => 'string', 'enum' => ['', 'CNY', 'cny', 'points']],
|
||||
'order_status' => ['type' => 'string', 'sanitize_callback' => 'sanitize_key'],
|
||||
'order_number' => ['type' => 'string', 'maxLength' => 64, 'sanitize_callback' => 'sanitize_text_field'],
|
||||
'date_from' => ['type' => 'string', 'format' => 'date'],
|
||||
'date_to' => ['type' => 'string', 'format' => 'date'],
|
||||
],
|
||||
]);
|
||||
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'],
|
||||
'args' => ['id' => ['type' => 'integer', 'minimum' => 1, 'required' => true]],
|
||||
]);
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/payment/withdrawals', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'get_withdrawals'],
|
||||
@@ -154,187 +111,6 @@ class PaymentController extends ControllerBase {
|
||||
|
||||
}
|
||||
|
||||
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.00', 'pending' => '0.00', 'settled' => '0.00', '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.00'];
|
||||
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';
|
||||
$amount = (string) ($row->total_amount ?? '0');
|
||||
if ($key === 'points') {
|
||||
$amount = preg_match('/^(0|[1-9]\d*)(?:\.0+)?$/', $amount, $matches) ? (int) $matches[1] : 0;
|
||||
} else {
|
||||
try {
|
||||
$amount = \AKSRT\Ledger\AssetAmount::balance($amount)->decimal();
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
$amount = '0.00';
|
||||
}
|
||||
}
|
||||
$series[$key][] = [
|
||||
'date' => (string) $row->period,
|
||||
'amount' => $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');
|
||||
$operation_key = \AKSRT\Ledger\OperationKey::from_request($request, 'admin-revenue-settle', get_current_user_id());
|
||||
if (is_wp_error($operation_key)) {
|
||||
return $this->error($operation_key->get_error_message(), (int) ($operation_key->get_error_data()['status'] ?? 400));
|
||||
}
|
||||
$result = \AKSRT\Payment\Revenue::get_instance()->settle_result($id, get_current_user_id(), $operation_key);
|
||||
if (is_wp_error($result)) {
|
||||
$data = $result->get_error_data();
|
||||
return $this->error($result->get_error_message(), is_array($data) ? (int) ($data['status'] ?? 400) : 400);
|
||||
}
|
||||
$entry = (array) ($result->entries[0] ?? []);
|
||||
$asset = (string) ($entry['asset'] ?? \AKSRT\Ledger\AssetType::BALANCE->value);
|
||||
$author_id = (int) ($entry['user_id'] ?? 0);
|
||||
$balance_minor = $result->balance($author_id, $asset);
|
||||
$balance = $asset === \AKSRT\Ledger\AssetType::BALANCE->value
|
||||
? \AKSRT\Ledger\AssetAmount::decimal_from_minor($balance_minor)
|
||||
: $balance_minor;
|
||||
return $this->success([
|
||||
'message' => __('创作分成已结算', 'aksrt'),
|
||||
'transaction_id' => $result->transaction_id,
|
||||
'replayed' => $result->replayed,
|
||||
'asset' => $asset,
|
||||
'balance' => $balance,
|
||||
]);
|
||||
}
|
||||
|
||||
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' => []]]);
|
||||
|
||||
Reference in New Issue
Block a user