新增全局流水号

This commit is contained in:
2026-08-25 05:33:01 +08:00
parent 90fd59b647
commit 07c4e969c3
33 changed files with 1176 additions and 73 deletions
+195
View File
@@ -0,0 +1,195 @@
<?php
/**
* @package AKSRT
*/
namespace AKSRT\Admin\Native;
defined('ABSPATH') || exit;
final class PayFlows {
public const CAPABILITY = 'manage_options';
public const PAGE_SLUG = 'aksrt_flows';
private static ?self $instance = null;
private function __construct() {}
public static function get_instance(): self {
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
public function render_page(): void {
if (!current_user_can(self::CAPABILITY)) {
wp_die(esc_html__('您没有权限访问此页面。', 'aksrt'));
}
$keyword = isset($_GET['s']) ? sanitize_text_field((string) wp_unslash($_GET['s'])) : '';
$event_type = isset($_GET['event_type']) ? sanitize_key((string) wp_unslash($_GET['event_type'])) : '';
$asset = isset($_GET['asset']) ? sanitize_key((string) wp_unslash($_GET['asset'])) : '';
$direction = isset($_GET['direction']) ? sanitize_key((string) wp_unslash($_GET['direction'])) : '';
$user_id = isset($_GET['user_id']) ? max(0, (int) $_GET['user_id']) : 0;
$date_from = isset($_GET['date_from']) ? $this->sanitize_date((string) wp_unslash($_GET['date_from'])) : '';
$date_to = isset($_GET['date_to']) ? $this->sanitize_date((string) wp_unslash($_GET['date_to'])) : '';
$page = max(1, (int) ($_GET['paged'] ?? 1));
$query = \AKSRT\Finance\FinancialFlowQuery::get_instance();
$result = $query->search([
'keyword' => $keyword,
'event_type' => $event_type,
'asset' => $asset,
'direction' => $direction,
'user_id' => $user_id,
'date_from' => $this->date_bound_utc($date_from, false),
'date_to' => $this->date_bound_utc($date_to, true),
'page' => $page,
'per_page' => 20,
]);
$pages = max(1, (int) ceil($result['total'] / $result['per_page']));
echo '<div class="wrap"><h1>' . esc_html__('流水明细', 'aksrt') . '</h1>';
echo '<p class="description">' . esc_html__('仅展示已经确认发生的现金、余额和积分变动;流水不可编辑。', 'aksrt') . '</p>';
echo '<form method="get" style="margin:12px 0;display:flex;gap:6px;align-items:center;flex-wrap:wrap;">';
echo '<input type="hidden" name="page" value="' . esc_attr(self::PAGE_SLUG) . '">';
echo '<input type="search" name="s" value="' . esc_attr($keyword) . '" placeholder="' . esc_attr__('流水号 / 订单号 / 提现单号 / 渠道交易号', 'aksrt') . '" style="min-width:280px;">';
echo '<input type="number" min="1" name="user_id" value="' . esc_attr($user_id > 0 ? (string) $user_id : '') . '" placeholder="' . esc_attr__('用户 ID', 'aksrt') . '" class="small-text">';
echo '<select name="event_type"><option value="">' . esc_html__('全部事件', 'aksrt') . '</option>';
foreach ($query->event_types() as $key => $label) {
echo '<option value="' . esc_attr($key) . '"' . selected($event_type, $key, false) . '>' . esc_html($this->event_label($label)) . '</option>';
}
echo '</select>';
$this->render_select('asset', $asset, __('全部资产', 'aksrt'), ['cash' => __('现金', 'aksrt'), 'balance' => __('余额', 'aksrt'), 'points' => __('积分', 'aksrt')]);
$this->render_select('direction', $direction, __('全部方向', 'aksrt'), ['in' => __('收入', 'aksrt'), 'out' => __('支出', 'aksrt')]);
echo '<label>' . esc_html__('从', 'aksrt') . ' <input type="date" name="date_from" value="' . esc_attr($date_from) . '"></label>';
echo '<label>' . esc_html__('至', 'aksrt') . ' <input type="date" name="date_to" value="' . esc_attr($date_to) . '"></label>';
submit_button(__('筛选', 'aksrt'), 'secondary', '', false);
if ($keyword !== '' || $event_type !== '' || $asset !== '' || $direction !== '' || $user_id > 0 || $date_from !== '' || $date_to !== '') {
echo '<a class="button" href="' . esc_url($this->page_url()) . '">' . esc_html__('清除条件', 'aksrt') . '</a>';
}
echo '</form>';
echo '<p>' . esc_html(sprintf(__('共 %d 条流水', 'aksrt'), (int) $result['total'])) . '</p>';
echo '<table class="wp-list-table widefat fixed striped"><thead><tr>';
echo '<th style="width:180px">' . esc_html__('流水号 / 时间', 'aksrt') . '</th>';
echo '<th style="width:130px">' . 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 ($result['items'] === []) {
echo '<tr><td colspan="5">' . esc_html__('暂无匹配流水', 'aksrt') . '</td></tr>';
}
foreach ($result['items'] as $flow) {
echo '<tr>';
echo '<td><code>' . esc_html((string) $flow['flow_number']) . '</code><br><span class="description">' . esc_html($this->local_time((string) $flow['created_at'])) . '</span></td>';
echo '<td><strong>' . esc_html($this->event_label((string) $flow['event_type'])) . '</strong><br><code>' . esc_html((string) $flow['event_type']) . '</code></td>';
echo '<td>' . $this->entries_html((array) $flow['entries']) . '</td>';
echo '<td>' . $this->reference_html($flow) . '</td>';
echo '<td>' . ((string) ($flow['provider_transaction_id'] ?? '') !== '' ? '<code>' . esc_html((string) $flow['provider_transaction_id']) . '</code>' : '&mdash;') . '</td>';
echo '</tr>';
}
echo '</tbody></table>';
if ($pages > 1) {
echo '<div class="tablenav"><div class="tablenav-pages">' . wp_kses_post((string) paginate_links([
'base' => add_query_arg('paged', '%#%'),
'format' => '',
'prev_text' => '&laquo;',
'next_text' => '&raquo;',
'total' => $pages,
'current' => $page,
])) . '</div></div>';
}
echo '</div>';
}
/** @param array<string,string> $options */
private function render_select(string $name, string $value, string $empty_label, array $options): void {
echo '<select name="' . esc_attr($name) . '"><option value="">' . esc_html($empty_label) . '</option>';
foreach ($options as $key => $label) {
echo '<option value="' . esc_attr($key) . '"' . selected($value, $key, false) . '>' . esc_html($label) . '</option>';
}
echo '</select>';
}
/** @param array<int,array<string,mixed>> $entries */
private function entries_html(array $entries): string {
if ($entries === []) {
return '&mdash;';
}
$lines = [];
foreach ($entries as $entry) {
$user_id = (int) ($entry['user_id'] ?? 0);
$user = $user_id > 0 ? get_userdata($user_id) : null;
$user_label = $user ? $user->display_name . ' (#' . $user_id . ')' : '#' . $user_id;
$asset = (string) ($entry['asset'] ?? '');
$delta = (int) ($entry['delta_minor'] ?? 0);
$amount = $asset === 'points'
? (string) abs($delta)
: \AKSRT\Ledger\AssetAmount::from_minor(\AKSRT\Ledger\AssetType::BALANCE, abs($delta))->decimal();
$asset_label = ['cash' => __('现金', 'aksrt'), 'balance' => __('余额', 'aksrt'), 'points' => __('积分', 'aksrt')][$asset] ?? $asset;
$lines[] = esc_html($user_label) . ' · ' . esc_html($asset_label) . ' <strong>' . ($delta > 0 ? '+' : '-') . esc_html($amount) . '</strong>';
}
return implode('<br>', $lines);
}
/** @param array<string,mixed> $flow */
private function reference_html(array $flow): string {
$lines = [];
if (!empty($flow['order_number'])) {
$url = add_query_arg(['page' => PayOrders::PAGE_SLUG, 'order' => (string) $flow['order_number']], admin_url('admin.php'));
$lines[] = esc_html__('订单', 'aksrt') . ' <a href="' . esc_url($url) . '"><code>' . esc_html((string) $flow['order_number']) . '</code></a>';
}
if (!empty($flow['withdrawal_number'])) {
$lines[] = esc_html__('提现', 'aksrt') . ' <code>' . esc_html((string) $flow['withdrawal_number']) . '</code>';
}
if ($lines === [] && !empty($flow['ref_key'])) {
$lines[] = esc_html((string) ($flow['ref_type'] ?? '')) . ' · ' . esc_html((string) $flow['ref_key']);
}
return $lines !== [] ? implode('<br>', $lines) : '&mdash;';
}
private function event_label(string $event_type): string {
$labels = [
'order_payment' => __('订单支付', 'aksrt'),
'order_refund' => __('订单退款', 'aksrt'),
'withdraw_hold' => __('提现冻结', 'aksrt'),
'withdraw_refund' => __('提现退回', 'aksrt'),
'withdraw_payout' => __('提现打款', 'aksrt'),
'creator_income' => __('创作收益', 'aksrt'),
'admin_adjustment' => __('后台调账', 'aksrt'),
'points_exchange' => __('积分兑换', 'aksrt'),
'transfer' => __('用户转账', 'aksrt'),
];
return $labels[$event_type] ?? $event_type;
}
private function sanitize_date(string $date): string {
return preg_match('/^\d{4}-\d{2}-\d{2}$/', $date) === 1 ? $date : '';
}
private function date_bound_utc(string $date, bool $exclusive_end): string {
if ($date === '') {
return '';
}
$local = new \DateTimeImmutable($date . ' 00:00:00', wp_timezone());
if ($exclusive_end) {
$local = $local->modify('+1 day');
}
return $local->setTimezone(new \DateTimeZone('UTC'))->format('Y-m-d H:i:s');
}
private function local_time(string $utc): string {
if ($utc === '') {
return '';
}
try {
return (new \DateTimeImmutable($utc, new \DateTimeZone('UTC')))->setTimezone(wp_timezone())->format('Y-m-d H:i:s');
} catch (\Exception $e) {
return $utc;
}
}
private function page_url(): string {
return add_query_arg(['page' => self::PAGE_SLUG], admin_url('admin.php'));
}
}
+7
View File
@@ -52,6 +52,13 @@ class PayMenus {
wp_die(esc_html__('订单模块未初始化。', 'aksrt'));
},
],
'flows' => [
'slug' => PayFlows::PAGE_SLUG,
'title' => __('流水明细', 'aksrt'),
'callback' => static function (): void {
PayFlows::get_instance()->render_page();
},
],
'products' => [
'slug' => 'aksrt_pay_products',
'title' => __('付费文章', 'aksrt'),
+37 -11
View File
@@ -79,6 +79,10 @@ class PayOrders {
}
$items = $repo->get_orders_admin($args, $search);
$flow_numbers = \AKSRT\Finance\FinancialFlowQuery::get_instance()->order_payment_number_map(array_map(
static fn(array $row): string => (string) ($row['order_number'] ?? ''),
$items
));
$total = $repo->get_orders_count($args, $search);
$pages = max(1, (int) ceil($total / $per_page));
$labels = class_exists('\AKSRT\Core\DomainModel')
@@ -102,6 +106,7 @@ class PayOrders {
'user_id' => $user_id > 0 ? $user_id : null,
'payment_method' => $pay_method !== '' ? $pay_method : null,
'status' => ($status !== '' && $status !== 'all') ? $status : null,
's' => $search !== '' ? $search : null,
]));
$cls = ($product_type === $tkey) ? 'current' : '';
$tab_bits[] = '<li><a class="' . esc_attr($cls) . '" href="' . esc_url($url) . '">' . esc_html($tlabel) . '</a></li>';
@@ -126,6 +131,7 @@ class PayOrders {
'user_id' => $user_id > 0 ? $user_id : null,
'payment_method' => $mkey !== '' ? $mkey : null,
'status' => ($status !== '' && $status !== 'all') ? $status : null,
's' => $search !== '' ? $search : null,
]));
$cls = ($pay_method === $mkey) ? 'current' : '';
$m_bits[] = '<li><a class="' . esc_attr($cls) . '" href="' . esc_url($url) . '">' . esc_html($mlabel) . '</a></li>';
@@ -156,7 +162,7 @@ class PayOrders {
}
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" /> ';
echo '<input type="search" name="s" value="' . esc_attr($search) . '" placeholder="' . esc_attr__('订单号 / 类型 / 用户', 'aksrt') . '" /> ';
echo '<input type="search" name="s" value="' . esc_attr($search) . '" placeholder="' . esc_attr__('订单号 / 流水号 / 类型 / 用户', 'aksrt') . '" /> ';
submit_button(__('筛选', 'aksrt'), 'secondary', '', false);
if ($product_id > 0 || $product_type !== '' || $user_id > 0 || $pay_method !== '') {
$filter_bits = [];
@@ -189,12 +195,12 @@ class PayOrders {
echo '<table class="wp-list-table widefat fixed striped">';
echo '<thead><tr>';
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 '<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>';
@@ -208,14 +214,22 @@ class PayOrders {
$user = $user_id > 0 ? get_userdata($user_id) : null;
$status_key = (string) ($row['status'] ?? '');
$status_label = $labels[$status_key] ?? $status_key;
$flow_number = (string) ($flow_numbers[$order_number] ?? '');
$product_id = (int) ($row['product_id'] ?? 0);
$product_type = sanitize_key((string) ($row['product_type'] ?? ''));
$meta = is_array($row['meta_payload'] ?? null) ? $row['meta_payload'] : [];
$product_title = (string) ($meta['product_title'] ?? $meta['title'] ?? $meta['package_title'] ?? '');
if ($product_title === '' && $product_id > 0 && in_array($product_type, ['resource', 'shop_product'], true)) {
$product_title = (string) get_the_title($product_id);
}
echo '<tr>';
echo '<td><code>' . esc_html($order_number) . '</code></td>';
echo '<td><code>' . esc_html($order_number) . '</code><br><span class="description">' . esc_html__('流水:', 'aksrt') . ($flow_number !== '' ? '<code>' . esc_html($flow_number) . '</code>' : '&mdash;') . '</span></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($this->cash_decimal((string) ($row['amount'] ?? '0'))) . ' ' . esc_html((string) ($row['currency'] ?? '')) . '</td>';
echo '<td><strong>' . esc_html($product_title !== '' ? $product_title : $product_type) . '</strong><br><span class="description">' . esc_html($product_type) . ($product_id > 0 ? ' #' . esc_html((string) $product_id) : '') . '</span></td>';
echo '<td><strong>' . esc_html($this->order_amount($row)) . '</strong><br><span class="description">' . esc_html((string) ($row['payment_method'] ?? '')) . '</span></td>';
echo '<td>' . esc_html($status_label) . '</td>';
echo '<td>' . esc_html((string) ($row['created_at'] ?? '')) . '</td>';
echo '<td>' . esc_html((string) ($row['created_at'] ?? '')) . '<br><span class="description">' . esc_html((string) ($row['paid_at'] ?? '') ?: '—') . '</span></td>';
echo '<td style="display:flex;gap:6px;flex-wrap:wrap;align-items:center;">';
echo '<a class="button button-small" href="' . esc_url($this->page_url('aksrt_orders', ['order' => $order_number])) . '">' . esc_html__('详情', 'aksrt') . '</a>';
$this->render_order_quick_status_buttons($order_number, $status_key);
@@ -262,6 +276,7 @@ class PayOrders {
? \AKSRT\Core\DomainModel::order_status_labels()
: [];
$context = $repo->get_admin_order_context($order_number);
$flow_number = \AKSRT\Finance\FinancialFlowQuery::get_instance()->order_payment_number($order_number);
$user_id = (int) ($order['user_id'] ?? 0);
$user = $user_id > 0 ? get_userdata($user_id) : null;
$status = (string) ($order['status'] ?? '');
@@ -277,13 +292,14 @@ class PayOrders {
echo '<table class="widefat striped" style="max-width:960px;"><tbody>';
$rows = [
__('订单号', 'aksrt') => $order_number,
__('支付流水号', 'aksrt') => $flow_number !== '' ? $flow_number : '—',
__('用户', 'aksrt') => $user ? $user->display_name . ' (#' . $user_id . ')' : (string) $user_id,
__('商品类型', 'aksrt') => (string) ($order['product_type'] ?? ''),
__('商品 ID', 'aksrt') => (string) ($order['product_id'] ?? ''),
__('金额', 'aksrt') => $this->cash_decimal((string) ($order['amount'] ?? '0')) . ' ' . (string) ($order['currency'] ?? ''),
__('金额', 'aksrt') => $this->order_amount($order),
__('状态', 'aksrt') => $labels[$status] ?? $status,
__('支付方式', 'aksrt') => (string) ($order['payment_method'] ?? ''),
__('交易号', 'aksrt') => (string) ($order['payment_transaction_id'] ?? ''),
__('渠道交易号', 'aksrt') => (string) ($order['payment_transaction_id'] ?? ''),
__('创建时间', 'aksrt') => (string) ($order['created_at'] ?? ''),
__('支付时间', 'aksrt') => (string) ($order['paid_at'] ?? ''),
__('买家备注', 'aksrt') => $customer_note,
@@ -440,6 +456,16 @@ class PayOrders {
}
}
/** @param array<string,mixed> $order */
private function order_amount(array $order): string {
$currency = sanitize_key((string) ($order['currency'] ?? ''));
$method = sanitize_key((string) ($order['payment_method'] ?? ''));
if ($currency === 'points' || $method === 'points') {
return max(0, (int) ($order['amount'] ?? 0)) . ' ' . __('积分', 'aksrt');
}
return $this->cash_decimal((string) ($order['amount'] ?? '0')) . ' ' . strtoupper((string) ($order['currency'] ?? 'CNY'));
}
private function render_header(string $title): void {
echo '<div class="wrap">';
echo '<h1>' . esc_html($title) . '</h1>';
+41 -21
View File
@@ -39,6 +39,10 @@ class PayWithdrawals {
}
$status = isset($_GET['status']) ? sanitize_key((string) wp_unslash($_GET['status'])) : '';
$keyword = isset($_GET['s']) ? sanitize_text_field((string) wp_unslash($_GET['s'])) : '';
$user_filter = isset($_GET['user_id']) ? max(0, (int) $_GET['user_id']) : 0;
$date_from = isset($_GET['date_from']) ? $this->sanitize_date((string) wp_unslash($_GET['date_from'])) : '';
$date_to = isset($_GET['date_to']) ? $this->sanitize_date((string) wp_unslash($_GET['date_to'])) : '';
$page_num = max(1, (int) ($_GET['paged'] ?? 1));
$per_page = 20;
$svc = \AKSRT\Payment\Withdrawal::get_instance();
@@ -79,31 +83,47 @@ class PayWithdrawals {
);
}
echo '</select> ';
echo '<input type="search" name="s" value="' . esc_attr($keyword) . '" placeholder="' . esc_attr__('提现单号 / 用户 ID', 'aksrt') . '" /> ';
echo '<input type="number" min="1" name="user_id" value="' . esc_attr($user_filter > 0 ? (string) $user_filter : '') . '" placeholder="' . esc_attr__('用户 ID', 'aksrt') . '" class="small-text" /> ';
echo '<label>' . esc_html__('从', 'aksrt') . ' <input type="date" name="date_from" value="' . esc_attr($date_from) . '"></label> ';
echo '<label>' . esc_html__('至', 'aksrt') . ' <input type="date" name="date_to" value="' . esc_attr($date_to) . '"></label> ';
submit_button(__('筛选', 'aksrt'), 'secondary', '', false);
if ($status !== '' || $keyword !== '' || $user_filter > 0 || $date_from !== '' || $date_to !== '') {
echo ' <a class="button" href="' . esc_url($this->page_url(self::PAGE_SLUG)) . '">' . esc_html__('清除条件', 'aksrt') . '</a>';
}
echo '</form>';
$items = $svc->get_all([
'status' => $status,
'keyword' => $keyword,
'user_id' => $user_filter,
'date_from' => $date_from,
'date_to' => $date_to,
'limit' => $per_page,
'offset' => ($page_num - 1) * $per_page,
]);
$total = $svc->get_count(['status' => $status]);
$total = $svc->get_count([
'status' => $status,
'keyword' => $keyword,
'user_id' => $user_filter,
'date_from' => $date_from,
'date_to' => $date_to,
]);
$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 '<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>';
echo '<tr><td colspan="7">' . esc_html__('暂无提现记录', 'aksrt') . '</td></tr>';
}
foreach ($items as $row) {
@@ -115,25 +135,21 @@ class PayWithdrawals {
$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'),
$gross,
$net,
$fee
);
}
$amount_txt = sprintf(
/* translators: 1: gross 2: net 3: fee percent */
__('申请 %1$s / 到账 %2$s(手续费 %3$d%%)', 'aksrt'),
$gross,
$net,
$fee
);
echo '<tr>';
echo '<td>' . esc_html((string) $id) . '</td>';
echo '<td><code>' . esc_html((string) ($row['withdrawal_number'] ?? '')) . '</code></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><strong>' . esc_html((string) ($row['method'] ?? '')) . '</strong><br><span class="description">' . esc_html($acct) . '</span></td>';
echo '<td>' . esc_html($status_options[$st] ?? $st) . '</td>';
echo '<td>' . esc_html((string) ($row['created_at'] ?? '')) . '</td>';
echo '<td>' . esc_html((string) ($row['created_at'] ?? '')) . '<br><span class="description">' . esc_html((string) ($row['processed_at'] ?? '') ?: '—') . '</span></td>';
echo '<td>';
$next_statuses = $st === 'pending' ? ['processing', 'rejected'] : ($st === 'processing' ? ['completed', 'rejected'] : []);
if ($next_statuses === []) {
@@ -258,6 +274,10 @@ class PayWithdrawals {
return add_query_arg(array_merge(['page' => $slug], $args), admin_url('admin.php'));
}
private function sanitize_date(string $date): string {
return preg_match('/^\d{4}-\d{2}-\d{2}$/', $date) === 1 ? $date : '';
}
private function render_pagination(int $pages, int $page_num): void {
if ($pages <= 1) {
return;
+2
View File
@@ -234,6 +234,8 @@ class SchemaPayment {
'groups' => [
['key' => 'basic', 'title' => '订单基础', 'fields' => [
['key' => 'payment_order_prefix', 'label' => '订单前缀', 'type' => 'input', 'default' => 'AKSRT'],
['key' => 'payment_flow_prefix', 'label' => '流水号前缀', 'type' => 'input', 'sanitize_type' => 'business_prefix', 'default' => 'AK', 'maxlength' => 12, 'help' => '仅允许字母、数字、下划线和短横线,最多 12 位。'],
['key' => 'payment_withdrawal_prefix', 'label' => '提现单号前缀', 'type' => 'input', 'sanitize_type' => 'business_prefix', 'default' => 'TX', 'maxlength' => 12, 'help' => '仅允许字母、数字、下划线和短横线,最多 12 位。'],
['key' => 'payment_order_expiry', 'label' => '订单有效期(分钟)', 'type' => 'number', 'default' => 30, 'min' => 5, 'max' => 1440],
['key' => 'pay_show_user', 'label' => '显示用户信息', 'type' => 'switch', 'default' => true],
['key' => 'pay_show_paycount', 'label' => '显示购买次数', 'type' => 'switch', 'default' => true],
+3
View File
@@ -517,6 +517,9 @@ class SchemaProvider {
private function sanitize_field_value($value, array $field) {
$type = (string) ($field['type'] ?? 'text');
$sanitize_type = (string) ($field['sanitize_type'] ?? '');
if ($sanitize_type === 'business_prefix') {
return \AKSRT\Finance\BusinessNumber::normalize_prefix($value, (string) ($field['default'] ?? 'AK'));
}
if ($sanitize_type !== '') {
return $this->sanitize_value($value, $sanitize_type);
}