feat(admin): cash wallet adjust on user profile

Mirror credits admin with list column, profile form, and balance ledger
for manual wallet adjustments.
This commit is contained in:
2026-07-16 16:29:15 +08:00
parent 15a883eb54
commit c3eee3fa7e
2 changed files with 165 additions and 0 deletions
@@ -0,0 +1,162 @@
<?php
/**
* Admin cash wallet adjust on WP user screens.
*
* @package AKSRT
*/
defined('ABSPATH') || exit;
class AKSRT_User_Balance_Admin {
private static $instance = null;
private function __construct() {}
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_filter('manage_users_columns', [$this, 'add_column']);
add_filter('manage_users_custom_column', [$this, 'render_column'], 10, 3);
add_filter('manage_users_sortable_columns', [$this, 'sortable']);
add_action('pre_get_users', [$this, 'sort_users']);
add_action('show_user_profile', [$this, 'render_section']);
add_action('edit_user_profile', [$this, 'render_section']);
add_action('personal_options_update', [$this, 'handle_update']);
add_action('edit_user_profile_update', [$this, 'handle_update']);
}
private function can_manage(): bool {
return current_user_can('manage_options');
}
public function add_column(array $columns): array {
if (!class_exists('AKSRT_Balance') || !AKSRT_Balance::enabled()) {
return $columns;
}
$columns['aksrt_wallet_balance'] = __('现金余额', 'aksrt');
return $columns;
}
public function render_column(string $output, string $column_name, int $user_id): string {
if ($column_name !== 'aksrt_wallet_balance') {
return $output;
}
if (!class_exists('AKSRT_Balance')) {
return '—';
}
return '<strong>' . esc_html(AKSRT_Balance::format(AKSRT_Balance::balance($user_id))) . '</strong>';
}
public function sortable(array $columns): array {
$columns['aksrt_wallet_balance'] = 'aksrt_wallet_balance';
return $columns;
}
public function sort_users($query): void {
if (!is_admin() || $query->get('orderby') !== 'aksrt_wallet_balance') {
return;
}
$query->set('meta_key', 'aksrt_balance');
$query->set('orderby', 'meta_value_num');
}
public function render_section(WP_User $user): void {
if (!class_exists('AKSRT_Balance') || !AKSRT_Balance::enabled()) {
return;
}
$balance = AKSRT_Balance::balance((int) $user->ID);
$can = $this->can_manage();
$log = AKSRT_Balance::get_log(['user_id' => (int) $user->ID, 'page' => 1, 'per_page' => 8]);
?>
<a id="aksrt-user-wallet"></a>
<h2><?php esc_html_e('现金钱包(AKSRT)', 'aksrt'); ?></h2>
<table class="form-table" role="presentation">
<tr>
<th><?php esc_html_e('当前余额', 'aksrt'); ?></th>
<td><strong style="font-size:18px;"><?php echo esc_html(AKSRT_Balance::format($balance)); ?></strong> <?php esc_html_e('元', 'aksrt'); ?></td>
</tr>
<?php if ($can) : ?>
<tr>
<th><?php esc_html_e('调整余额', 'aksrt'); ?></th>
<td>
<?php wp_nonce_field('aksrt_user_balance_adjust_' . (int) $user->ID, '_aksrt_balance_nonce'); ?>
<label for="aksrt_balance_delta"><?php esc_html_e('变动金额(正数=增加,负数=扣减)', 'aksrt'); ?></label><br>
<input type="number" step="0.01" name="aksrt_balance_delta" id="aksrt_balance_delta" value="" class="regular-text" />
<p>
<label for="aksrt_balance_reason"><?php esc_html_e('备注', 'aksrt'); ?></label><br>
<input type="text" name="aksrt_balance_reason" id="aksrt_balance_reason" class="regular-text" />
</p>
<p class="description"><?php esc_html_e('保存用户资料时生效,并写入余额流水。', 'aksrt'); ?></p>
</td>
</tr>
<?php endif; ?>
<tr>
<th><?php esc_html_e('近期流水', 'aksrt'); ?></th>
<td>
<?php if (!empty($log['items'])) : ?>
<table class="widefat striped" style="max-width:640px;">
<thead><tr><th><?php esc_html_e('时间', 'aksrt'); ?></th><th><?php esc_html_e('变动', 'aksrt'); ?></th><th><?php esc_html_e('余额', 'aksrt'); ?></th><th><?php esc_html_e('原因', 'aksrt'); ?></th></tr></thead>
<tbody>
<?php foreach ($log['items'] as $row) : ?>
<tr>
<td><?php echo esc_html(get_date_from_gmt((string) ($row['created_at'] ?? ''), 'Y-m-d H:i')); ?></td>
<td><?php echo esc_html(($row['delta'] > 0 ? '+' : '') . AKSRT_Balance::format((float) $row['delta'])); ?></td>
<td><?php echo esc_html(AKSRT_Balance::format((float) ($row['balance_after'] ?? 0))); ?></td>
<td><?php echo esc_html(AKSRT_Balance::reason_label((string) ($row['reason'] ?? ''))); ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php else : ?>
<p><?php esc_html_e('暂无流水', 'aksrt'); ?></p>
<?php endif; ?>
</td>
</tr>
</table>
<?php
}
public function handle_update(int $user_id): void {
if (!$this->can_manage() || !class_exists('AKSRT_Balance') || !AKSRT_Balance::enabled()) {
return;
}
if (empty($_POST['_aksrt_balance_nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash((string) $_POST['_aksrt_balance_nonce'])), 'aksrt_user_balance_adjust_' . $user_id)) {
return;
}
if (!isset($_POST['aksrt_balance_delta']) || $_POST['aksrt_balance_delta'] === '') {
return;
}
$delta = round((float) wp_unslash($_POST['aksrt_balance_delta']), 2);
if (abs($delta) < 0.0001) {
return;
}
$reason = isset($_POST['aksrt_balance_reason'])
? sanitize_text_field(wp_unslash((string) $_POST['aksrt_balance_reason']))
: __('管理员调整', 'aksrt');
if ($delta > 0) {
$result = AKSRT_Balance::add($user_id, $delta, 'admin', ['type' => 'user', 'id' => get_current_user_id()], $reason);
} else {
$result = AKSRT_Balance::deduct($user_id, abs($delta), 'admin', ['type' => 'user', 'id' => get_current_user_id()], $reason);
}
if (is_wp_error($result)) {
set_transient('aksrt_admin_notice_' . get_current_user_id(), [[
'type' => 'error',
'msg' => $result->get_error_message(),
]], 30);
return;
}
set_transient('aksrt_admin_notice_' . get_current_user_id(), [[
'type' => 'success',
'msg' => __('现金余额已更新', 'aksrt'),
]], 30);
}
}