Files
wordpress-theme-aksrt/inc/classes/admin/native/class-aksrt-admin-pay-withdrawals.php
T
kate522 4da003663d fix(pay): refund wallet on withdraw reject and show pending history
Close the freeze/refund loop when admin rejects a balance withdrawal,
block concurrent open applications, and surface processing state plus
server-rendered withdraw history on the wallet card.
2026-07-16 16:28:56 +08:00

261 lines
10 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* Native WP admin: AKSRT_Admin_Pay_Withdrawals.
*
* Extracted from AKSRT_Admin_Pay_Menus.
*
* @package AKSRT
*/
defined('ABSPATH') || exit;
class AKSRT_Admin_Pay_Withdrawals {
public const CAPABILITY = 'manage_options';
public const PAGE_SLUG = 'aksrt_withdraw';
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_update_withdrawal', [$this, 'handle_update_withdrawal']);
}
public function render_withdrawals_page(): void {
$this->ensure_cap();
$this->render_header(__('提现审核', 'aksrt'));
if (!class_exists('AKSRT_Withdrawal')) {
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'])) : '';
$page_num = max(1, (int) ($_GET['paged'] ?? 1));
$per_page = 20;
$svc = AKSRT_Withdrawal::get_instance();
$stats = $svc->get_stats();
echo '<p>' . esc_html(sprintf(
/* translators: 1: total 2: pending 3: completed 4: paid */
__('共 %1$d 条 · 待处理 %2$d · 已完成 %3$d · 已打款 %4$s', 'aksrt'),
(int) ($stats['total'] ?? 0),
(int) ($stats['pending'] ?? 0),
(int) ($stats['completed'] ?? 0),
number_format((float) ($stats['total_paid'] ?? 0), 2)
)) . '</p>';
echo '<form method="get" style="margin:12px 0;">';
echo '<input type="hidden" name="page" value="aksrt_withdraw" />';
echo '<select name="status">';
$status_options = [
'' => __('全部状态', 'aksrt'),
'pending' => __('待处理', 'aksrt'),
'approved' => __('已通过', 'aksrt'),
'processing' => __('处理中', 'aksrt'),
'completed' => __('已完成', 'aksrt'),
'rejected' => __('已拒绝', 'aksrt'),
];
foreach ($status_options as $key => $label) {
printf(
'<option value="%s"%s>%s</option>',
esc_attr($key),
selected($status, $key, false),
esc_html($label)
);
}
echo '</select> ';
submit_button(__('筛选', 'aksrt'), 'secondary', '', false);
echo '</form>';
$items = $svc->get_all([
'status' => $status,
'limit' => $per_page,
'offset' => ($page_num - 1) * $per_page,
]);
$total = $svc->get_count(['status' => $status]);
$pages = max(1, (int) ceil($total / $per_page));
echo '<table class="wp-list-table widefat fixed striped">';
echo '<thead><tr>';
echo '<th>ID</th>';
echo '<th>' . esc_html__('用户', 'aksrt') . '</th>';
echo '<th>' . esc_html__('金额', 'aksrt') . '</th>';
echo '<th>' . esc_html__('方式', 'aksrt') . '</th>';
echo '<th>' . esc_html__('账户', 'aksrt') . '</th>';
echo '<th>' . esc_html__('状态', 'aksrt') . '</th>';
echo '<th>' . esc_html__('申请时间', 'aksrt') . '</th>';
echo '<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['user_id'] ?? 0);
$user = $user_id > 0 ? get_userdata($user_id) : null;
$st = (string) ($row['status'] ?? '');
$gross = (float) ($row['gross_amount'] ?? $row['amount'] ?? 0);
$net = (float) ($row['amount'] ?? 0);
$fee = (int) ($row['fee_rate'] ?? 0);
$acct = (string) ($row['display_account'] ?? $row['account_info'] ?? '');
$amount_txt = number_format($net, 2);
if ($gross > 0 && abs($gross - $net) > 0.001) {
$amount_txt = sprintf(
/* translators: 1: gross 2: net 3: fee percent */
__('申请 %1$s / 到账 %2$s(手续费 %3$d%%)', 'aksrt'),
number_format($gross, 2),
number_format($net, 2),
$fee
);
}
echo '<tr>';
echo '<td>' . esc_html((string) $id) . '</td>';
echo '<td>' . ($user ? esc_html($user->display_name . ' (#' . $user_id . ')') : esc_html((string) $user_id)) . '</td>';
echo '<td>' . esc_html($amount_txt) . '</td>';
echo '<td>' . esc_html((string) ($row['method'] ?? '')) . '</td>';
echo '<td>' . esc_html($acct) . '</td>';
echo '<td>' . esc_html($status_options[$st] ?? $st) . '</td>';
echo '<td>' . esc_html((string) ($row['created_at'] ?? '')) . '</td>';
echo '<td>';
echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" style="display:flex;gap:4px;flex-wrap:wrap;align-items:center;">';
echo '<input type="hidden" name="action" value="aksrt_pay_update_withdrawal" />';
echo '<input type="hidden" name="withdrawal_id" value="' . esc_attr((string) $id) . '" />';
wp_nonce_field('aksrt_pay_update_withdrawal_' . $id);
echo '<select name="new_status">';
foreach ($status_options as $key => $label) {
if ($key === '') {
continue;
}
printf(
'<option value="%s"%s>%s</option>',
esc_attr($key),
selected($st, $key, false),
esc_html($label)
);
}
echo '</select>';
echo '<input type="text" name="note" placeholder="' . esc_attr__('备注', 'aksrt') . '" value="" />';
submit_button(__('更新', 'aksrt'), 'small', '', false);
echo '</form>';
echo '</td>';
echo '</tr>';
}
echo '</tbody></table>';
if ($pages > 1) {
echo '<div class="tablenav"><div class="tablenav-pages">';
echo paginate_links([
'base' => add_query_arg('paged', '%#%'),
'format' => '',
'prev_text' => '&laquo;',
'next_text' => '&raquo;',
'total' => $pages,
'current' => $page_num,
]);
echo '</div></div>';
}
$this->render_footer();
}
public function handle_update_withdrawal(): void {
if (!current_user_can(self::CAPABILITY)) {
wp_die(esc_html__('权限不足', 'aksrt'));
}
$id = isset($_POST['withdrawal_id']) ? (int) $_POST['withdrawal_id'] : 0;
$new_status = isset($_POST['new_status']) ? sanitize_key((string) wp_unslash($_POST['new_status'])) : '';
$note = isset($_POST['note']) ? sanitize_textarea_field((string) wp_unslash($_POST['note'])) : '';
check_admin_referer('aksrt_pay_update_withdrawal_' . $id);
$msg = 'withdraw_failed';
if ($id > 0 && $new_status !== '' && class_exists('AKSRT_Withdrawal')) {
$result = AKSRT_Withdrawal::get_instance()->process_status_change($id, $new_status, $note);
if (!is_wp_error($result) && $result) {
$msg = 'withdraw_updated';
} elseif (is_wp_error($result) && $result->get_error_code() === 'aksrt_balance') {
$msg = 'withdraw_refund_failed';
}
}
wp_safe_redirect($this->page_url('aksrt_withdraw', [
'aksrt_pay_msg' => $msg,
]));
exit;
}
/* ------------------------------------------------------------------
* Income / rebate
* ----------------------------------------------------------------*/
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 = [
'withdraw_updated' => __('提现状态已更新。', 'aksrt'),
'withdraw_failed' => __('提现状态更新失败。', 'aksrt'),
'withdraw_refund_failed' => __('状态未更新:余额退回失败,请检查钱包后重试。', 'aksrt'),
];
if (!isset($map[$code])) {
return;
}
$class = 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' => '&laquo;',
'next_text' => '&raquo;',
'total' => $pages,
'current' => $page_num,
]);
echo '</div></div>';
}
}