feat(admin): redesign withdrawal review page and drop dead withdrawal code

- Split PayWithdrawals into shell + PayWithdrawalsList flat sibling trait
- Add process/policy callout, open/paid/fee stats, status views with counts
- Filter by source and payout method; reset keeps the status view
- Show net payout with gross/fee breakdown, copyable payee, note column
- Replace inline select form with per-row actions and a confirm dialog;
  reject requires a reason; double-submit guard; retry reuses the
  idempotency key; redirect back preserving filters and paging
- Withdrawal: add get_status_counts() and method filter; get_stats() now
  returns open_gross / paid_net / paid_fee
- Remove unused REST routes (/payment/withdrawals, /transition),
  admin_withdrawals(), update_status(), has_open_withdrawal(),
  count_open(), resolve_gross_amount()

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
kate522andClaude Fable 5 committed 2026-10-31 21:06:52 +08:00
1 parent 1d6bc8f7cc
commit d98f8d7975
9 files changed
+862 -414

No files matched your search

-91
View File
@@ -18,96 +18,5 @@ class PaymentController extends ControllerBase {
public function register_routes(): void {
$this->register_section_routes('payment', 'payment');
register_rest_route(self::API_NAMESPACE, '/payment/withdrawals', [
'methods' => 'GET',
'callback' => [$this, 'get_withdrawals'],
'permission_callback' => [$this, 'check_permission'],
'args' => [
'page' => ['type' => 'integer', 'minimum' => 1, 'default' => 1],
'per_page' => ['type' => 'integer', 'minimum' => 1, 'maximum' => 50, 'default' => 20],
'status' => ['type' => 'string', 'enum' => ['', 'pending', 'processing', 'completed', 'rejected']],
],
]);
// 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'],
'args' => [
'id' => ['type' => 'integer', 'minimum' => 1, 'required' => true],
'status' => ['type' => 'string', 'required' => true, 'enum' => ['processing', 'completed', 'rejected']],
'note' => ['type' => 'string', 'default' => '', 'maxLength' => 1000, 'sanitize_callback' => 'sanitize_textarea_field'],
],
]);
}
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 = ['processing', 'completed', 'rejected'];
if (!in_array($new_status, $valid_statuses, true)) {
return $this->error('无效的提现状态');
}
if ($id <= 0) {
return $this->error('无效的提现 ID');
}
$operation_key = \AKSRT\Ledger\OperationKey::from_request($request, 'admin-withdraw-transition', 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\Withdrawal::get_instance()->process_status_change($id, $new_status, $note, $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);
}
if (is_array($result)) {
return $this->success(array_merge(['message' => __('提现状态已更新', 'aksrt')], $result));
}
return $this->error(__('更新失败', 'aksrt'));
}
}