积分与余额账务系统硬切重构
This commit is contained in:
@@ -50,7 +50,6 @@ class PayChargeCards {
|
||||
'balance' => __('余额', 'aksrt'),
|
||||
'vip' => __('VIP', 'aksrt'),
|
||||
'credits' => __('积分', 'aksrt'),
|
||||
'points' => __('积分', 'aksrt'),
|
||||
];
|
||||
$status_labels = [
|
||||
'active' => __('可用', 'aksrt'),
|
||||
@@ -81,7 +80,7 @@ class PayChargeCards {
|
||||
echo '<p><label>' . esc_html__('前缀', 'aksrt') . '<br><input type="text" name="prefix" value="" /></label></p>';
|
||||
echo '<p><label>' . esc_html__('面额/积分', 'aksrt') . '<br><input type="number" step="0.01" name="amount" value="10" /></label></p>';
|
||||
echo '<p><label>' . esc_html__('VIP等级', 'aksrt') . '<br><input type="number" name="vip_level" min="1" max="2" value="1" /></label></p>';
|
||||
echo '<p><label>' . esc_html__('VIP天数', 'aksrt') . '<br><input type="number" name="vip_days" min="1" value="30" /></label></p>';
|
||||
echo '<p><label>' . esc_html__('VIP天数', 'aksrt') . '<br><input type="number" name="days" min="1" value="30" /></label></p>';
|
||||
echo '<p><label>' . esc_html__('过期时间', 'aksrt') . '<br><input type="datetime-local" name="expires_at" /></label></p>';
|
||||
echo '<p><label>' . esc_html__('备注', 'aksrt') . '<br><input type="text" name="note" value="" /></label></p>';
|
||||
submit_button(__('生成', 'aksrt'), 'primary', '', false);
|
||||
@@ -179,7 +178,7 @@ class PayChargeCards {
|
||||
echo '<tr>';
|
||||
echo '<td>' . esc_html((string) $id) . '</td>';
|
||||
echo '<td>' . esc_html($type_labels[$row_type] ?? $row_type) . '</td>';
|
||||
echo '<td><code>' . esc_html((string) ($row['password'] ?? $row['code'] ?? '')) . '</code></td>';
|
||||
echo '<td><code>' . esc_html((string) ($row['password'] ?? '')) . '</code></td>';
|
||||
echo '<td>' . esc_html((string) ($row['amount'] ?? '')) . '</td>';
|
||||
echo '<td>' . esc_html($status_labels[$st] ?? $st) . '</td>';
|
||||
echo '<td>' . esc_html((string) ($row['expires_at'] ?? '—')) . '</td>';
|
||||
@@ -252,10 +251,10 @@ class PayChargeCards {
|
||||
$count = isset($_POST['count']) ? (int) $_POST['count'] : 10;
|
||||
$length = isset($_POST['length']) ? (int) $_POST['length'] : 16;
|
||||
$prefix = isset($_POST['prefix']) ? sanitize_text_field((string) wp_unslash($_POST['prefix'])) : '';
|
||||
$amount = isset($_POST['amount']) ? (float) wp_unslash($_POST['amount']) : 0;
|
||||
$amount = isset($_POST['amount']) ? trim(sanitize_text_field((string) wp_unslash($_POST['amount']))) : '';
|
||||
$note = isset($_POST['note']) ? sanitize_textarea_field((string) wp_unslash($_POST['note'])) : '';
|
||||
$level = isset($_POST['vip_level']) ? max(1, min(2, (int) $_POST['vip_level'])) : 1;
|
||||
$days = isset($_POST['vip_days']) ? max(1, (int) $_POST['vip_days']) : 30;
|
||||
$days = isset($_POST['days']) ? max(1, (int) $_POST['days']) : 30;
|
||||
$expires = isset($_POST['expires_at']) ? sanitize_text_field((string) wp_unslash($_POST['expires_at'])) : '';
|
||||
if ($expires !== '') {
|
||||
$expires = str_replace('T', ' ', $expires);
|
||||
@@ -264,15 +263,30 @@ class PayChargeCards {
|
||||
}
|
||||
}
|
||||
|
||||
if ($type === 'points') {
|
||||
$type = 'credits';
|
||||
if (!in_array($type, ['balance', 'vip', 'credits'], true)) {
|
||||
wp_safe_redirect($this->page_url('aksrt_charge_cards', ['aksrt_pay_msg' => 'card_batch_failed']));
|
||||
exit;
|
||||
}
|
||||
|
||||
$meta = [];
|
||||
if ($type === 'balance') {
|
||||
$meta['amount'] = max(0, $amount);
|
||||
try {
|
||||
$balance_amount = \AKSRT\Ledger\AssetAmount::balance($amount);
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
wp_safe_redirect($this->page_url('aksrt_charge_cards', ['aksrt_pay_msg' => 'card_batch_failed']));
|
||||
exit;
|
||||
}
|
||||
if ($balance_amount->minor() <= 0) {
|
||||
wp_safe_redirect($this->page_url('aksrt_charge_cards', ['aksrt_pay_msg' => 'card_batch_failed']));
|
||||
exit;
|
||||
}
|
||||
$meta['amount'] = $balance_amount->decimal();
|
||||
} elseif ($type === 'credits') {
|
||||
$meta['points'] = max(0, (int) $amount);
|
||||
if (!preg_match('/^[1-9]\d*$/', $amount)) {
|
||||
wp_safe_redirect($this->page_url('aksrt_charge_cards', ['aksrt_pay_msg' => 'card_batch_failed']));
|
||||
exit;
|
||||
}
|
||||
$meta['points'] = (int) $amount;
|
||||
} elseif ($type === 'vip') {
|
||||
$meta['level'] = $level;
|
||||
$meta['days'] = $days;
|
||||
|
||||
@@ -255,9 +255,9 @@ class PayCoupons {
|
||||
$id = \AKSRT\Payment\Coupon::get_instance()->create([
|
||||
'code' => sanitize_text_field((string) wp_unslash($_POST['code'] ?? '')),
|
||||
'type' => sanitize_key((string) wp_unslash($_POST['type'] ?? 'fixed')),
|
||||
'value' => (float) ($_POST['value'] ?? 0),
|
||||
'min_amount' => (float) ($_POST['min_amount'] ?? 0),
|
||||
'max_discount' => (float) ($_POST['max_discount'] ?? 0),
|
||||
'value' => trim((string) wp_unslash($_POST['value'] ?? '0')),
|
||||
'min_amount' => trim((string) wp_unslash($_POST['min_amount'] ?? '0')),
|
||||
'max_discount' => trim((string) wp_unslash($_POST['max_discount'] ?? '0')),
|
||||
'usage_limit' => (int) ($_POST['usage_limit'] ?? 0),
|
||||
'per_user_limit' => (int) ($_POST['per_user_limit'] ?? 0),
|
||||
'product_scope' => $this->parse_product_scope_from_request(),
|
||||
@@ -281,9 +281,9 @@ class PayCoupons {
|
||||
if (class_exists('\AKSRT\Payment\Coupon')) {
|
||||
$result = \AKSRT\Payment\Coupon::get_instance()->batch_create([
|
||||
'type' => sanitize_key((string) wp_unslash($_POST['type'] ?? 'fixed')),
|
||||
'value' => (float) ($_POST['value'] ?? 0),
|
||||
'min_amount' => (float) ($_POST['min_amount'] ?? 0),
|
||||
'max_discount' => (float) ($_POST['max_discount'] ?? 0),
|
||||
'value' => trim((string) wp_unslash($_POST['value'] ?? '0')),
|
||||
'min_amount' => trim((string) wp_unslash($_POST['min_amount'] ?? '0')),
|
||||
'max_discount' => trim((string) wp_unslash($_POST['max_discount'] ?? '0')),
|
||||
'usage_limit' => (int) ($_POST['usage_limit'] ?? 0),
|
||||
'per_user_limit' => (int) ($_POST['per_user_limit'] ?? 0),
|
||||
'product_scope' => $this->parse_product_scope_from_request(),
|
||||
@@ -315,9 +315,9 @@ class PayCoupons {
|
||||
if ($id > 0 && class_exists('\AKSRT\Payment\Coupon')) {
|
||||
$ok = \AKSRT\Payment\Coupon::get_instance()->update($id, [
|
||||
'type' => sanitize_key((string) wp_unslash($_POST['type'] ?? 'fixed')),
|
||||
'value' => (float) ($_POST['value'] ?? 0),
|
||||
'min_amount' => (float) ($_POST['min_amount'] ?? 0),
|
||||
'max_discount' => (float) ($_POST['max_discount'] ?? 0),
|
||||
'value' => trim((string) wp_unslash($_POST['value'] ?? '0')),
|
||||
'min_amount' => trim((string) wp_unslash($_POST['min_amount'] ?? '0')),
|
||||
'max_discount' => trim((string) wp_unslash($_POST['max_discount'] ?? '0')),
|
||||
'usage_limit' => (int) ($_POST['usage_limit'] ?? 0),
|
||||
'per_user_limit' => (int) ($_POST['per_user_limit'] ?? 0),
|
||||
'product_scope' => $this->parse_product_scope_from_request(),
|
||||
|
||||
@@ -60,18 +60,26 @@ class PayIncome {
|
||||
|
||||
$cash_stats = (array) ($stats['cash'] ?? []);
|
||||
$points_stats = (array) ($stats['points'] ?? []);
|
||||
$cash_amount = static function ($value): string {
|
||||
try {
|
||||
return \AKSRT\Ledger\AssetAmount::balance(is_string($value) ? $value : '')->formatted();
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
return '0.00';
|
||||
}
|
||||
};
|
||||
echo '<p>' . esc_html(sprintf(
|
||||
__('现金:合计 ¥%1$s · 待结算 ¥%2$s · 已结算 ¥%3$s;积分:合计 %4$s · 待结算 %5$s · 已结算 %6$s', 'aksrt'),
|
||||
number_format((float) ($cash_stats['total'] ?? 0), 2),
|
||||
number_format((float) ($cash_stats['pending'] ?? 0), 2),
|
||||
number_format((float) ($cash_stats['settled'] ?? 0), 2),
|
||||
number_format((float) ($points_stats['total'] ?? 0), 0),
|
||||
number_format((float) ($points_stats['pending'] ?? 0), 0),
|
||||
number_format((float) ($points_stats['settled'] ?? 0), 0)
|
||||
$cash_amount($cash_stats['total'] ?? '0'),
|
||||
$cash_amount($cash_stats['pending'] ?? '0'),
|
||||
$cash_amount($cash_stats['settled'] ?? '0'),
|
||||
number_format_i18n(max(0, (int) ($points_stats['total'] ?? 0))),
|
||||
number_format_i18n(max(0, (int) ($points_stats['pending'] ?? 0))),
|
||||
number_format_i18n(max(0, (int) ($points_stats['settled'] ?? 0)))
|
||||
)) . '</p>';
|
||||
|
||||
echo '<form id="aksrt-pay-batch-settle-income" method="post" action="' . esc_url(admin_url('admin-post.php')) . '" style="margin:12px 0;">';
|
||||
echo '<input type="hidden" name="action" value="aksrt_pay_batch_settle_income" />';
|
||||
echo '<input type="hidden" name="operation_key" value="' . esc_attr(wp_generate_uuid4()) . '" />';
|
||||
wp_nonce_field('aksrt_pay_batch_settle_income');
|
||||
submit_button(__('批量结算选中记录', 'aksrt'), 'secondary', '', false);
|
||||
echo '</form>';
|
||||
@@ -135,8 +143,10 @@ class PayIncome {
|
||||
echo '<td><code>' . esc_html((string) ($row['order_number'] ?? '')) . '</code></td>';
|
||||
$is_points = (string) ($row['currency'] ?? 'CNY') === 'points';
|
||||
$row_currency = $is_points ? __('积分', 'aksrt') : '¥';
|
||||
$row_decimals = $is_points ? 0 : 2;
|
||||
echo '<td>' . esc_html(number_format((float) ($row['amount'] ?? 0), $row_decimals) . ' ' . $row_currency) . '</td>';
|
||||
$row_amount = $is_points
|
||||
? number_format_i18n(max(0, (int) ($row['amount'] ?? 0)))
|
||||
: $cash_amount($row['amount'] ?? '0');
|
||||
echo '<td>' . esc_html($row_amount . ' ' . $row_currency) . '</td>';
|
||||
echo '<td>' . esc_html($st) . '</td>';
|
||||
echo '<td>' . esc_html((string) ($row['created_at'] ?? '')) . '</td>';
|
||||
echo '<td>';
|
||||
@@ -144,6 +154,7 @@ class PayIncome {
|
||||
echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '">';
|
||||
echo '<input type="hidden" name="action" value="aksrt_pay_settle_income" />';
|
||||
echo '<input type="hidden" name="revenue_id" value="' . esc_attr((string) $id) . '" />';
|
||||
echo '<input type="hidden" name="operation_key" value="' . esc_attr(wp_generate_uuid4()) . '" />';
|
||||
wp_nonce_field('aksrt_pay_settle_income_' . $id);
|
||||
submit_button(__('结算', 'aksrt'), 'small', '', false);
|
||||
echo '</form>';
|
||||
@@ -163,7 +174,11 @@ class PayIncome {
|
||||
}
|
||||
$id = isset($_POST['revenue_id']) ? (int) $_POST['revenue_id'] : 0;
|
||||
check_admin_referer('aksrt_pay_settle_income_' . $id);
|
||||
$ok = $id > 0 && class_exists('\AKSRT\Payment\Revenue') && \AKSRT\Payment\Revenue::get_instance()->settle($id, get_current_user_id());
|
||||
$request_key = sanitize_text_field((string) wp_unslash($_POST['operation_key'] ?? ''));
|
||||
$operation_key = wp_is_uuid($request_key)
|
||||
? \AKSRT\Ledger\OperationKey::deterministic('admin-revenue-settle', [get_current_user_id(), strtolower($request_key)])
|
||||
: '';
|
||||
$ok = $id > 0 && $operation_key !== '' && class_exists('\AKSRT\Payment\Revenue') && \AKSRT\Payment\Revenue::get_instance()->settle($id, get_current_user_id(), $operation_key);
|
||||
wp_safe_redirect($this->page_url('aksrt_income', ['aksrt_pay_msg' => $ok ? 'income_settled' : 'income_failed']));
|
||||
exit;
|
||||
}
|
||||
@@ -187,8 +202,12 @@ class PayIncome {
|
||||
}
|
||||
}
|
||||
$ids = array_values(array_unique($ids));
|
||||
$batch_result = !empty($ids) && class_exists('\\AKSRT\\Payment\\Revenue')
|
||||
? \AKSRT\Payment\Revenue::get_instance()->settle_batch($ids, get_current_user_id())
|
||||
$request_key = sanitize_text_field((string) wp_unslash($_POST['operation_key'] ?? ''));
|
||||
$operation_key = wp_is_uuid($request_key)
|
||||
? \AKSRT\Ledger\OperationKey::deterministic('admin-revenue-settle-batch', [get_current_user_id(), strtolower($request_key)])
|
||||
: '';
|
||||
$batch_result = !empty($ids) && $operation_key !== '' && class_exists('\\AKSRT\\Payment\\Revenue')
|
||||
? \AKSRT\Payment\Revenue::get_instance()->settle_batch($ids, get_current_user_id(), $operation_key)
|
||||
: ['settled' => [], 'failed' => $ids];
|
||||
$message = empty($batch_result['settled'])
|
||||
? 'income_failed'
|
||||
|
||||
@@ -107,17 +107,6 @@ class PayMenus {
|
||||
wp_die(esc_html__('分成模块未初始化。', 'aksrt'));
|
||||
},
|
||||
],
|
||||
'rebate' => [
|
||||
'slug' => 'aksrt_rebate',
|
||||
'title' => __('推广返佣', 'aksrt'),
|
||||
'callback' => static function (): void {
|
||||
if (class_exists('AKSRT\\Admin\\Native\\PayRebate')) {
|
||||
PayRebate::get_instance()->render_rebate_page();
|
||||
return;
|
||||
}
|
||||
wp_die(esc_html__('返佣模块未初始化。', 'aksrt'));
|
||||
},
|
||||
],
|
||||
'withdraw' => [
|
||||
'slug' => 'aksrt_withdraw',
|
||||
'title' => __('提现审核', 'aksrt'),
|
||||
@@ -224,7 +213,7 @@ class PayMenus {
|
||||
$this->stat_card(__('订单总数', 'aksrt'), (string) (int) ($order_stats['total'] ?? 0), $this->page_url('aksrt_orders'));
|
||||
$this->stat_card(__('待支付', 'aksrt'), (string) (int) ($order_stats['pending'] ?? 0), $this->page_url('aksrt_orders', ['status' => 'pending']));
|
||||
$this->stat_card(__('已支付', 'aksrt'), (string) (int) ($order_stats['paid'] ?? 0), $this->page_url('aksrt_orders', ['status' => 'paid']));
|
||||
$this->stat_card(__('营收', 'aksrt'), number_format((float) ($order_stats['revenue'] ?? 0), 2), $this->page_url('aksrt_orders'));
|
||||
$this->stat_card(__('营收', 'aksrt'), (string) ($order_stats['revenue'] ?? '0.00'), $this->page_url('aksrt_orders'));
|
||||
$this->stat_card(__('待审提现', 'aksrt'), (string) (int) ($withdraw_stats['pending'] ?? 0), $this->page_url('aksrt_withdraw', ['status' => 'pending']));
|
||||
$this->stat_card(__('可用卡密', 'aksrt'), (string) (int) ($card_stats['active'] ?? 0), $this->page_url('aksrt_charge_cards', ['status' => 'active']));
|
||||
echo '</div>';
|
||||
|
||||
@@ -213,7 +213,7 @@ class PayOrders {
|
||||
echo '<td><code>' . esc_html($order_number) . '</code></td>';
|
||||
echo '<td>' . ($user ? esc_html($user->display_name . ' (#' . $user_id . ')') : esc_html((string) $user_id)) . '</td>';
|
||||
echo '<td>' . esc_html((string) ($row['product_type'] ?? '')) . '</td>';
|
||||
echo '<td>' . esc_html(number_format((float) ($row['amount'] ?? 0), 2)) . ' ' . esc_html((string) ($row['currency'] ?? '')) . '</td>';
|
||||
echo '<td>' . esc_html($this->cash_decimal((string) ($row['amount'] ?? '0'))) . ' ' . esc_html((string) ($row['currency'] ?? '')) . '</td>';
|
||||
echo '<td>' . esc_html($status_label) . '</td>';
|
||||
echo '<td>' . esc_html((string) ($row['created_at'] ?? '')) . '</td>';
|
||||
echo '<td style="display:flex;gap:6px;flex-wrap:wrap;align-items:center;">';
|
||||
@@ -296,7 +296,7 @@ class PayOrders {
|
||||
__('用户', 'aksrt') => $user ? $user->display_name . ' (#' . $user_id . ')' : (string) $user_id,
|
||||
__('商品类型', 'aksrt') => (string) ($order['product_type'] ?? ''),
|
||||
__('商品 ID', 'aksrt') => (string) ($order['product_id'] ?? ''),
|
||||
__('金额', 'aksrt') => number_format((float) ($order['amount'] ?? 0), 2) . ' ' . (string) ($order['currency'] ?? ''),
|
||||
__('金额', 'aksrt') => $this->cash_decimal((string) ($order['amount'] ?? '0')) . ' ' . (string) ($order['currency'] ?? ''),
|
||||
__('状态', 'aksrt') => $labels[$status] ?? $status,
|
||||
__('支付方式', 'aksrt') => (string) ($order['payment_method'] ?? ''),
|
||||
__('交易号', 'aksrt') => (string) ($order['payment_transaction_id'] ?? ''),
|
||||
@@ -465,6 +465,14 @@ class PayOrders {
|
||||
}
|
||||
}
|
||||
|
||||
private function cash_decimal(string $value): string {
|
||||
try {
|
||||
return \AKSRT\Ledger\AssetAmount::balance($value)->decimal();
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
return '0.00';
|
||||
}
|
||||
}
|
||||
|
||||
private function render_header(string $title): void {
|
||||
echo '<div class="wrap">';
|
||||
echo '<h1>' . esc_html($title) . '</h1>';
|
||||
|
||||
@@ -94,17 +94,16 @@ class PayProducts {
|
||||
$modo_label = $modo === 'points' ? __('积分', 'aksrt') : __('现金', 'aksrt');
|
||||
|
||||
if ($modo === 'points') {
|
||||
$list_price = (int) ceil((float) ($meta['points_price'] ?? $meta['price'] ?? 0));
|
||||
$list_price = max(0, (int) ($meta['points_price'] ?? 0));
|
||||
$price_text = sprintf(__('%d 积分', 'aksrt'), $list_price);
|
||||
} else {
|
||||
$list_price = round((float) ($meta['price'] ?? 0), 2);
|
||||
$price_text = number_format($list_price, 2);
|
||||
$price_text = $this->cash_decimal((string) ($meta['price'] ?? '0'));
|
||||
}
|
||||
|
||||
$sales = $sales_map[$post_id] ?? ['sales_count' => 0, 'avg_amount' => 0.0, 'revenue' => 0.0];
|
||||
$sales = $sales_map[$post_id] ?? ['sales_count' => 0, 'avg_amount' => '0.00', 'revenue' => '0.00'];
|
||||
$db_sales = (int) ($sales['sales_count'] ?? 0);
|
||||
$avg = (float) ($sales['avg_amount'] ?? 0);
|
||||
$revenue = (float) ($sales['revenue'] ?? 0);
|
||||
$avg = $this->cash_decimal((string) ($sales['avg_amount'] ?? '0'));
|
||||
$revenue = $this->cash_decimal((string) ($sales['revenue'] ?? '0'));
|
||||
$meta_sales = (int) get_post_meta($post_id, \AKSRT\Core\DomainModel::RESOURCE_SALES_VOLUME_KEY, true);
|
||||
|
||||
$edit_url = get_edit_post_link($post_id, 'raw');
|
||||
@@ -130,10 +129,10 @@ class PayProducts {
|
||||
echo '<td>' . esc_html((string) $pt_label) . '</td>';
|
||||
echo '<td>' . esc_html($modo_label) . '</td>';
|
||||
echo '<td>' . esc_html($price_text) . '</td>';
|
||||
echo '<td>' . esc_html(number_format($avg, 2)) . '</td>';
|
||||
echo '<td>' . esc_html($avg) . '</td>';
|
||||
echo '<td>' . esc_html((string) $db_sales) . '</td>';
|
||||
echo '<td>' . esc_html((string) max(0, $meta_sales)) . '</td>';
|
||||
echo '<td>' . esc_html(number_format($revenue, 2)) . '</td>';
|
||||
echo '<td>' . esc_html($revenue) . '</td>';
|
||||
echo '<td style="display:flex;gap:6px;flex-wrap:wrap;">';
|
||||
if ($edit_url) {
|
||||
echo '<a class="button button-small" href="' . esc_url($edit_url) . '">' . esc_html__('编辑', 'aksrt') . '</a>';
|
||||
@@ -337,6 +336,14 @@ class PayProducts {
|
||||
}
|
||||
}
|
||||
|
||||
private function cash_decimal(string $value): string {
|
||||
try {
|
||||
return \AKSRT\Ledger\AssetAmount::balance($value)->decimal();
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
return '0.00';
|
||||
}
|
||||
}
|
||||
|
||||
private function render_header(string $title): void {
|
||||
echo '<div class="wrap">';
|
||||
echo '<h1>' . esc_html($title) . '</h1>';
|
||||
|
||||
@@ -1,202 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Admin\Native;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class PayRebate {
|
||||
|
||||
public const CAPABILITY = 'manage_options';
|
||||
public const PAGE_SLUG = 'aksrt_rebate';
|
||||
|
||||
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_settle_rebate', [$this, 'handle_settle_rebate']);
|
||||
add_action('admin_post_aksrt_pay_cancel_rebate', [$this, 'handle_cancel_rebate']);
|
||||
}
|
||||
|
||||
public function render_rebate_page(): void {
|
||||
$this->ensure_cap();
|
||||
$this->render_header(__('推广返佣', 'aksrt'));
|
||||
|
||||
if (!class_exists('\AKSRT\Payment\Rebate')) {
|
||||
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'])) : '';
|
||||
$user_id = isset($_GET['user_id']) ? max(0, (int) $_GET['user_id']) : (isset($_GET['referrer_id']) ? max(0, (int) $_GET['referrer_id']) : 0);
|
||||
$page_num = max(1, (int) ($_GET['paged'] ?? 1));
|
||||
$per_page = 20;
|
||||
$svc = \AKSRT\Payment\Rebate::get_instance();
|
||||
$stats = $svc->get_stats();
|
||||
|
||||
echo '<p>' . esc_html(sprintf(
|
||||
/* translators: 1: total 2: pending 3: settled amount */
|
||||
__('记录 %1$d · 待结算 %2$d · 已结算金额 %3$s', 'aksrt'),
|
||||
(int) ($stats['total'] ?? 0),
|
||||
(int) ($stats['pending'] ?? 0),
|
||||
number_format((float) ($stats['settled_amount'] ?? 0), 2)
|
||||
)) . '</p>';
|
||||
|
||||
echo '<form method="get" style="margin:12px 0;">';
|
||||
echo '<input type="hidden" name="page" value="aksrt_rebate" />';
|
||||
echo '<select name="status">';
|
||||
foreach (['' => __('全部状态', 'aksrt'), 'pending' => __('待结算', 'aksrt'), 'settled' => __('已结算', 'aksrt'), 'cancelled' => __('已取消', 'aksrt')] as $key => $label) {
|
||||
printf('<option value="%s"%s>%s</option>', esc_attr($key), selected($status, $key, false), esc_html($label));
|
||||
}
|
||||
echo '</select> ';
|
||||
echo '<input type="number" min="0" name="user_id" value="' . esc_attr($user_id > 0 ? (string) $user_id : '') . '" placeholder="' . esc_attr__('推广用户 ID', 'aksrt') . '" class="small-text" /> ';
|
||||
submit_button(__('筛选', 'aksrt'), 'secondary', '', false);
|
||||
if ($user_id > 0) {
|
||||
echo ' <a class="button button-small" href="' . esc_url($this->page_url('aksrt_rebate')) . '">' . esc_html__('清除用户过滤', 'aksrt') . '</a>';
|
||||
}
|
||||
echo '</form>';
|
||||
|
||||
$list_args = [
|
||||
'status' => $status,
|
||||
'limit' => $per_page,
|
||||
'offset' => ($page_num - 1) * $per_page,
|
||||
];
|
||||
if ($user_id > 0) {
|
||||
$list_args['user_id'] = $user_id;
|
||||
}
|
||||
$result = $svc->get_list($list_args);
|
||||
$items = $result['items'] ?? [];
|
||||
$total = (int) ($result['total'] ?? 0);
|
||||
$pages = max(1, (int) ceil(max(1, $total) / $per_page));
|
||||
|
||||
echo '<table class="wp-list-table widefat fixed striped"><thead><tr>';
|
||||
echo '<th>ID</th><th>' . esc_html__('用户', 'aksrt') . '</th><th>' . esc_html__('订单号', 'aksrt') . '</th><th>' . esc_html__('金额', 'aksrt') . '</th><th>' . esc_html__('状态', 'aksrt') . '</th><th>' . esc_html__('时间', 'aksrt') . '</th><th>' . esc_html__('操作', 'aksrt') . '</th>';
|
||||
echo '</tr></thead><tbody>';
|
||||
if (!$items) {
|
||||
echo '<tr><td colspan="7">' . 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'] ?? '');
|
||||
echo '<tr>';
|
||||
echo '<td>' . esc_html((string) $id) . '</td>';
|
||||
echo '<td>' . ($user ? esc_html($user->display_name) : esc_html((string) $user_id)) . '</td>';
|
||||
echo '<td><code>' . esc_html((string) ($row['order_number'] ?? '')) . '</code></td>';
|
||||
echo '<td>' . esc_html(number_format((float) ($row['amount'] ?? 0), 2)) . '</td>';
|
||||
echo '<td>' . esc_html($st) . '</td>';
|
||||
echo '<td>' . esc_html((string) ($row['created_at'] ?? '')) . '</td>';
|
||||
echo '<td style="display:flex;gap:4px;">';
|
||||
if ($st === 'pending') {
|
||||
echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '">';
|
||||
echo '<input type="hidden" name="action" value="aksrt_pay_settle_rebate" />';
|
||||
echo '<input type="hidden" name="rebate_id" value="' . esc_attr((string) $id) . '" />';
|
||||
wp_nonce_field('aksrt_pay_settle_rebate_' . $id);
|
||||
submit_button(__('结算', 'aksrt'), 'small', '', false);
|
||||
echo '</form>';
|
||||
echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '">';
|
||||
echo '<input type="hidden" name="action" value="aksrt_pay_cancel_rebate" />';
|
||||
echo '<input type="hidden" name="rebate_id" value="' . esc_attr((string) $id) . '" />';
|
||||
wp_nonce_field('aksrt_pay_cancel_rebate_' . $id);
|
||||
submit_button(__('取消', 'aksrt'), 'small', '', false);
|
||||
echo '</form>';
|
||||
} else {
|
||||
echo '—';
|
||||
}
|
||||
echo '</td></tr>';
|
||||
}
|
||||
echo '</tbody></table>';
|
||||
$this->render_pagination($pages, $page_num);
|
||||
$this->render_footer();
|
||||
}
|
||||
|
||||
public function handle_settle_rebate(): void {
|
||||
if (!current_user_can(self::CAPABILITY)) {
|
||||
wp_die(esc_html__('权限不足', 'aksrt'));
|
||||
}
|
||||
$id = isset($_POST['rebate_id']) ? (int) $_POST['rebate_id'] : 0;
|
||||
check_admin_referer('aksrt_pay_settle_rebate_' . $id);
|
||||
$ok = $id > 0 && class_exists('\AKSRT\Payment\Rebate') && \AKSRT\Payment\Rebate::get_instance()->settle($id);
|
||||
wp_safe_redirect($this->page_url('aksrt_rebate', ['aksrt_pay_msg' => $ok ? 'rebate_settled' : 'rebate_failed']));
|
||||
exit;
|
||||
}
|
||||
|
||||
public function handle_cancel_rebate(): void {
|
||||
if (!current_user_can(self::CAPABILITY)) {
|
||||
wp_die(esc_html__('权限不足', 'aksrt'));
|
||||
}
|
||||
$id = isset($_POST['rebate_id']) ? (int) $_POST['rebate_id'] : 0;
|
||||
check_admin_referer('aksrt_pay_cancel_rebate_' . $id);
|
||||
$ok = $id > 0 && class_exists('\AKSRT\Payment\Rebate') && \AKSRT\Payment\Rebate::get_instance()->cancel($id);
|
||||
wp_safe_redirect($this->page_url('aksrt_rebate', ['aksrt_pay_msg' => $ok ? 'rebate_cancelled' : 'rebate_failed']));
|
||||
exit;
|
||||
}
|
||||
|
||||
|
||||
|
||||
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 = [
|
||||
'rebate_settled' => __('返佣已结算。', 'aksrt'),
|
||||
'rebate_cancelled' => __('返佣已取消。', 'aksrt'),
|
||||
'rebate_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' => '«',
|
||||
'next_text' => '»',
|
||||
'total' => $pages,
|
||||
'current' => $page_num,
|
||||
]);
|
||||
echo '</div></div>';
|
||||
}
|
||||
}
|
||||
@@ -43,6 +43,11 @@ class PayWithdrawals {
|
||||
$per_page = 20;
|
||||
$svc = \AKSRT\Payment\Withdrawal::get_instance();
|
||||
$stats = $svc->get_stats();
|
||||
$retry_id = isset($_GET['retry_id']) ? max(0, (int) $_GET['retry_id']) : 0;
|
||||
$retry_key = isset($_GET['retry_key']) ? sanitize_text_field((string) wp_unslash($_GET['retry_key'])) : '';
|
||||
if (!wp_is_uuid($retry_key)) {
|
||||
$retry_key = '';
|
||||
}
|
||||
|
||||
echo '<p>' . esc_html(sprintf(
|
||||
/* translators: 1: total 2: pending 3: completed 4: paid */
|
||||
@@ -52,7 +57,7 @@ class PayWithdrawals {
|
||||
(int) ($stats['processing'] ?? 0),
|
||||
(int) ($stats['completed'] ?? 0),
|
||||
(int) ($stats['rejected'] ?? 0),
|
||||
number_format((float) ($stats['total_paid'] ?? 0), 2)
|
||||
(string) ($stats['total_paid'] ?? '0.00')
|
||||
)) . '</p>';
|
||||
|
||||
echo '<form method="get" style="margin:12px 0;">';
|
||||
@@ -106,17 +111,17 @@ class PayWithdrawals {
|
||||
$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(
|
||||
$gross = (string) ($row['gross_amount'] ?? '0.00');
|
||||
$net = (string) ($row['amount'] ?? '0.00');
|
||||
$fee = (int) ($row['fee_rate'] ?? 0);
|
||||
$acct = (string) ($row['display_account'] ?? $row['account_info'] ?? '');
|
||||
$amount_txt = $net;
|
||||
if ($gross !== $net) {
|
||||
$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),
|
||||
$gross,
|
||||
$net,
|
||||
$fee
|
||||
);
|
||||
}
|
||||
@@ -130,15 +135,22 @@ class PayWithdrawals {
|
||||
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) . '" />';
|
||||
$next_statuses = $st === 'pending' ? ['processing', 'rejected'] : ($st === 'processing' ? ['completed', 'rejected'] : []);
|
||||
if ($next_statuses === []) {
|
||||
echo '—';
|
||||
echo '</td>';
|
||||
echo '</tr>';
|
||||
continue;
|
||||
}
|
||||
$request_key = $retry_id === $id && $retry_key !== '' ? $retry_key : wp_generate_uuid4();
|
||||
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) . '" />';
|
||||
echo '<input type="hidden" name="idempotency_key" value="' . esc_attr($request_key) . '" />';
|
||||
wp_nonce_field('aksrt_pay_update_withdrawal_' . $id);
|
||||
echo '<select name="new_status">';
|
||||
foreach ($status_options as $key => $label) {
|
||||
if ($key === '') {
|
||||
continue;
|
||||
}
|
||||
foreach ($next_statuses as $key) {
|
||||
$label = $status_options[$key];
|
||||
printf(
|
||||
'<option value="%s"%s>%s</option>',
|
||||
esc_attr($key),
|
||||
@@ -180,26 +192,31 @@ class PayWithdrawals {
|
||||
$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'])) : '';
|
||||
$request_key = isset($_POST['idempotency_key']) ? sanitize_text_field((string) wp_unslash($_POST['idempotency_key'])) : '';
|
||||
check_admin_referer('aksrt_pay_update_withdrawal_' . $id);
|
||||
|
||||
$msg = 'withdraw_failed';
|
||||
if ($id > 0 && $new_status !== '' && class_exists('\AKSRT\Payment\Withdrawal')) {
|
||||
$result = \AKSRT\Payment\Withdrawal::get_instance()->process_status_change($id, $new_status, $note);
|
||||
if ($id > 0 && $new_status !== '' && wp_is_uuid($request_key) && class_exists('\AKSRT\Payment\Withdrawal')) {
|
||||
$operation_key = \AKSRT\Ledger\OperationKey::deterministic('admin-withdraw-transition', [get_current_user_id(), strtolower($request_key)]);
|
||||
$result = \AKSRT\Payment\Withdrawal::get_instance()->process_status_change($id, $new_status, $note, $operation_key);
|
||||
if (!is_wp_error($result) && $result) {
|
||||
$msg = 'withdraw_updated';
|
||||
} elseif (is_wp_error($result) && $result->get_error_code() === 'aksrt_balance') {
|
||||
} elseif (is_wp_error($result) && $result->get_error_code() === 'aksrt_balance_insufficient') {
|
||||
$msg = 'withdraw_refund_failed';
|
||||
}
|
||||
}
|
||||
|
||||
wp_safe_redirect($this->page_url('aksrt_withdraw', [
|
||||
'aksrt_pay_msg' => $msg,
|
||||
]));
|
||||
$redirect_args = ['aksrt_pay_msg' => $msg];
|
||||
if ($msg !== 'withdraw_updated' && wp_is_uuid($request_key)) {
|
||||
$redirect_args['retry_id'] = $id;
|
||||
$redirect_args['retry_key'] = $request_key;
|
||||
}
|
||||
wp_safe_redirect($this->page_url('aksrt_withdraw', $redirect_args));
|
||||
exit;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
* Income / rebate
|
||||
* Income
|
||||
* ----------------------------------------------------------------*/
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user