diff --git a/assets/css/routes/user-center/credits.css b/assets/css/routes/user-center/credits.css
index 9f29d0c7..07ddc9f7 100644
--- a/assets/css/routes/user-center/credits.css
+++ b/assets/css/routes/user-center/credits.css
@@ -928,3 +928,41 @@ body.uc-modal-open {
}
.uc-card-pass__tip.is-error { color: #b91c1c; }
+
+
+/* Withdraw history + pending foot */
+.uc-asset-card__foot {
+ margin-top: 8px;
+ padding-top: 8px;
+ border-top: 1px dashed var(--muted-border-color, rgba(0,0,0,.08));
+}
+.uc-asset-card__foot button {
+ background: none;
+ border: 0;
+ padding: 0;
+ cursor: pointer;
+}
+.uc-asset-card__head-action {
+ background: none;
+ border: 0;
+ cursor: pointer;
+}
+.uc-withdraw-history__item:last-child {
+ border-bottom: 0 !important;
+}
+
+
+/* Points tasks tab */
+.uc-points-tasks .border-bottom,
+.uc-points-date .border-bottom,
+.uc-withdraw-history__item.border-bottom {
+ border-bottom: 1px solid var(--muted-border-color, rgba(0,0,0,.08));
+}
+.uc-points-tasks button,
+.uc-points-date button {
+ background: none;
+ border: 0;
+ cursor: pointer;
+ padding: 0;
+}
+.focus-color { color: var(--focus-color, #f59e0b); }
diff --git a/assets/js/credits.js b/assets/js/credits.js
index 36bc8fb8..efbe0433 100644
--- a/assets/js/credits.js
+++ b/assets/js/credits.js
@@ -908,10 +908,17 @@
link.addEventListener('click', function (e) {
e.preventDefault();
var key = link.getAttribute('data-aksrt-asset-tab');
- document.querySelectorAll('[data-aksrt-asset-tab]').forEach(function (a) {
+ document.querySelectorAll('.uc-asset-tabs__nav [data-aksrt-asset-tab]').forEach(function (a) {
var li = a.closest('li');
- if (li) li.classList.toggle('active', a === link);
+ if (!li) return;
+ var aKey = a.getAttribute('data-aksrt-asset-tab');
+ var on = aKey === key || (key === 'points_date' && aKey === 'points_free');
+ li.classList.toggle('active', on);
});
+ var tabs = document.querySelector('.uc-asset-tabs');
+ if (tabs && typeof tabs.scrollIntoView === 'function') {
+ tabs.scrollIntoView({ behavior: 'smooth', block: 'start' });
+ }
document.querySelectorAll('[data-aksrt-asset-pane]').forEach(function (pane) {
var on = pane.getAttribute('data-aksrt-asset-pane') === key;
pane.classList.toggle('is-active', on);
@@ -921,6 +928,20 @@
});
}
+
+ function initWithdrawHistory() {
+ var modal = document.querySelector('[data-aksrt-withdraw-history-modal]');
+ if (!modal) return;
+ function open() { modal.hidden = false; document.body.classList.add('uc-modal-open'); }
+ function close() { modal.hidden = true; document.body.classList.remove('uc-modal-open'); }
+ document.querySelectorAll('[data-aksrt-withdraw-history-open]').forEach(function (btn) {
+ btn.addEventListener('click', function (e) { e.preventDefault(); open(); });
+ });
+ modal.querySelectorAll('[data-aksrt-withdraw-history-close]').forEach(function (btn) {
+ btn.addEventListener('click', function (e) { e.preventDefault(); close(); });
+ });
+ }
+
function bootAssets() {
initSignin();
initInvite();
@@ -932,6 +953,7 @@
initExchange();
initPointsBuy();
initWithdraw();
+ initWithdrawHistory();
initAssetTabs();
}
diff --git a/inc/classes/admin/native/class-aksrt-admin-pay-withdrawals.php b/inc/classes/admin/native/class-aksrt-admin-pay-withdrawals.php
index f80e8a42..08197e28 100644
--- a/inc/classes/admin/native/class-aksrt-admin-pay-withdrawals.php
+++ b/inc/classes/admin/native/class-aksrt-admin-pay-withdrawals.php
@@ -107,13 +107,27 @@ class AKSRT_Admin_Pay_Withdrawals {
$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 '
';
echo '| ' . esc_html((string) $id) . ' | ';
echo '' . ($user ? esc_html($user->display_name . ' (#' . $user_id . ')') : esc_html((string) $user_id)) . ' | ';
- echo '' . esc_html(number_format((float) ($row['amount'] ?? 0), 2)) . ' | ';
+ echo '' . esc_html($amount_txt) . ' | ';
echo '' . esc_html((string) ($row['method'] ?? '')) . ' | ';
- echo '' . esc_html((string) ($row['account_info'] ?? '')) . ' | ';
+ echo '' . esc_html($acct) . ' | ';
echo '' . esc_html($status_options[$st] ?? $st) . ' | ';
echo '' . esc_html((string) ($row['created_at'] ?? '')) . ' | ';
echo '';
@@ -169,13 +183,18 @@ class AKSRT_Admin_Pay_Withdrawals {
$note = isset($_POST['note']) ? sanitize_textarea_field((string) wp_unslash($_POST['note'])) : '';
check_admin_referer('aksrt_pay_update_withdrawal_' . $id);
- $ok = false;
+ $msg = 'withdraw_failed';
if ($id > 0 && $new_status !== '' && class_exists('AKSRT_Withdrawal')) {
- $ok = AKSRT_Withdrawal::get_instance()->update_status($id, $new_status, $note);
+ $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' => $ok ? 'withdraw_updated' : 'withdraw_failed',
+ 'aksrt_pay_msg' => $msg,
]));
exit;
}
@@ -208,8 +227,9 @@ class AKSRT_Admin_Pay_Withdrawals {
}
$code = sanitize_key((string) wp_unslash($_GET['aksrt_pay_msg']));
$map = [
- 'withdraw_updated' => __('提现状态已更新。', 'aksrt'),
- 'withdraw_failed' => __('提现状态更新失败。', 'aksrt'),
+ 'withdraw_updated' => __('提现状态已更新。', 'aksrt'),
+ 'withdraw_failed' => __('提现状态更新失败。', 'aksrt'),
+ 'withdraw_refund_failed' => __('状态未更新:余额退回失败,请检查钱包后重试。', 'aksrt'),
];
if (!isset($map[$code])) {
return;
diff --git a/inc/classes/class-aksrt-balance-service.php b/inc/classes/class-aksrt-balance-service.php
index e7f22892..f4452fa2 100644
--- a/inc/classes/class-aksrt-balance-service.php
+++ b/inc/classes/class-aksrt-balance-service.php
@@ -734,6 +734,10 @@ class AKSRT_Balance_Service {
if ($account === '') {
return new WP_Error('aksrt_withdraw_account', __('请填写收款账号信息', 'aksrt'), ['status' => 400]);
}
+ // One open balance withdrawal at a time (Zibll parity).
+ if (AKSRT_Withdrawal::get_instance()->has_open_withdrawal($user_id)) {
+ return new WP_Error('aksrt_withdraw_pending', __('您有处理中的提现申请,请等待完成后再申请', 'aksrt'), ['status' => 409]);
+ }
$wallet = AKSRT_Balance::balance($user_id);
if ($wallet < $amount) {
return new WP_Error('aksrt_balance_insufficient', __('余额不足', 'aksrt'), ['status' => 402]);
diff --git a/inc/classes/class-aksrt-withdrawal.php b/inc/classes/class-aksrt-withdrawal.php
index 413ed541..8c8a363c 100644
--- a/inc/classes/class-aksrt-withdrawal.php
+++ b/inc/classes/class-aksrt-withdrawal.php
@@ -1,8 +1,6 @@
insert($this->table(), [
- 'user_id' => $user_id,
- 'amount' => $amount,
- 'method' => sanitize_key($method),
- 'account_info' => sanitize_textarea_field($account_info),
- 'status' => self::STATUS_PENDING,
- 'created_at' => current_time('mysql'),
- ], ['%d', '%f', '%s', '%s', '%s', '%s']);
+ $wpdb->insert($this->table(), [
+ 'user_id' => $user_id,
+ 'amount' => $amount,
+ 'method' => sanitize_key($method),
+ 'account_info' => sanitize_textarea_field($account_info),
+ 'status' => self::STATUS_PENDING,
+ 'created_at' => current_time('mysql'),
+ ], ['%d', '%f', '%s', '%s', '%s', '%s']);
- return (int) $wpdb->insert_id;
- }
+ return (int) $wpdb->insert_id;
+ }
- /**
- * 更新提现状态
- */
- public function update_status(int $withdrawal_id, string $new_status, string $note = ''): bool {
- global $wpdb;
+ /**
+ * Get single row.
+ *
+ * @return array|null
+ */
+ public function get(int $withdrawal_id): ?array {
+ global $wpdb;
+ if ($withdrawal_id <= 0) {
+ return null;
+ }
+ $row = $wpdb->get_row(
+ $wpdb->prepare("SELECT * FROM {$this->table()} WHERE id = %d LIMIT 1", $withdrawal_id),
+ ARRAY_A
+ );
+ return is_array($row) ? $row : null;
+ }
- $update = ['status' => sanitize_key($new_status)];
- $format = ['%s'];
+ /**
+ * 更新提现状态(无余额副作用)。优先使用 process_status_change()。
+ */
+ public function update_status(int $withdrawal_id, string $new_status, string $note = ''): bool {
+ global $wpdb;
- if ($new_status === self::STATUS_COMPLETED || $new_status === self::STATUS_REJECTED) {
- $update['processed_at'] = current_time('mysql');
- $format[] = '%s';
- }
+ $update = ['status' => sanitize_key($new_status)];
+ $format = ['%s'];
- if ($note !== '') {
- $update['note'] = sanitize_textarea_field($note);
- $format[] = '%s';
- }
+ if ($new_status === self::STATUS_COMPLETED || $new_status === self::STATUS_REJECTED) {
+ $update['processed_at'] = current_time('mysql');
+ $format[] = '%s';
+ }
- return $wpdb->update($this->table(), $update, ['id' => $withdrawal_id], $format, ['%d']) !== false;
- }
+ if ($note !== '') {
+ $update['note'] = sanitize_textarea_field($note);
+ $format[] = '%s';
+ }
- /**
- * 获取用户提现列表
- */
- public function get_user_withdrawals(int $user_id, array $args = []): array {
- global $wpdb;
+ return $wpdb->update($this->table(), $update, ['id' => $withdrawal_id], $format, ['%d']) !== false;
+ }
- $where = ['user_id = %d'];
- $params = [$user_id];
+ /**
+ * Status transition with balance side-effects.
+ * Reject of balance-source holds refunds gross_amount once (idempotent).
+ *
+ * @return true|WP_Error
+ */
+ public function process_status_change(int $withdrawal_id, string $new_status, string $note = '') {
+ $new_status = sanitize_key($new_status);
+ $allowed = [
+ self::STATUS_PENDING,
+ self::STATUS_APPROVED,
+ self::STATUS_PROCESSING,
+ self::STATUS_COMPLETED,
+ self::STATUS_REJECTED,
+ ];
+ if ($withdrawal_id <= 0 || !in_array($new_status, $allowed, true)) {
+ return new WP_Error('aksrt_withdraw_status', __('提现状态无效', 'aksrt'));
+ }
- $status = sanitize_key((string) ($args['status'] ?? ''));
- if ($status !== '') {
- $where[] = 'status = %s';
- $params[] = $status;
- }
+ $row = $this->get($withdrawal_id);
+ if (!$row) {
+ return new WP_Error('aksrt_withdraw_missing', __('提现记录不存在', 'aksrt'));
+ }
- $limit = max(1, min(100, (int) ($args['limit'] ?? 20)));
- $offset = max(0, (int) ($args['offset'] ?? 0));
+ $old_status = sanitize_key((string) ($row['status'] ?? ''));
+ if ($old_status === $new_status && $note === '') {
+ return true;
+ }
- $sql = "SELECT * FROM {$this->table()} WHERE " . implode(' AND ', $where) . " ORDER BY created_at DESC LIMIT %d OFFSET %d";
- $params[] = $limit;
- $params[] = $offset;
+ $meta = $this->parse_account_meta((string) ($row['account_info'] ?? ''));
+ $user_id = (int) ($row['user_id'] ?? 0);
- return (array) $wpdb->get_results($wpdb->prepare($sql, $params), ARRAY_A);
- }
+ // Refund frozen balance when moving into rejected (once).
+ if (
+ $new_status === self::STATUS_REJECTED
+ && $old_status !== self::STATUS_REJECTED
+ && empty($meta['balance_refunded'])
+ && $this->is_balance_source($meta)
+ && class_exists('AKSRT_Balance')
+ && $user_id > 0
+ ) {
+ $gross = $this->resolve_gross_amount($row, $meta);
+ if ($gross > 0) {
+ $refund = AKSRT_Balance::add(
+ $user_id,
+ $gross,
+ 'withdraw_refund',
+ ['type' => 'withdraw', 'id' => $withdrawal_id],
+ sprintf(__('提现拒绝退回 #%d', 'aksrt'), $withdrawal_id)
+ );
+ if (is_wp_error($refund)) {
+ return $refund;
+ }
+ $meta['balance_refunded'] = true;
+ $meta['refunded_at'] = current_time('mysql');
+ }
+ }
- /**
- * 获取所有提现列表(管理后台)
- */
- public function get_all(array $args = []): array {
- global $wpdb;
+ $account_info = $this->encode_account_meta($meta, (string) ($row['account_info'] ?? ''));
+ $merged_note = $this->merge_note((string) ($row['note'] ?? ''), $note);
- $where = [];
- $params = [];
+ global $wpdb;
+ $update = [
+ 'status' => $new_status,
+ 'account_info' => $account_info,
+ ];
+ $format = ['%s', '%s'];
- $status = sanitize_key((string) ($args['status'] ?? ''));
- if ($status !== '') {
- $where[] = 'status = %s';
- $params[] = $status;
- }
+ if ($new_status === self::STATUS_COMPLETED || $new_status === self::STATUS_REJECTED) {
+ $update['processed_at'] = current_time('mysql');
+ $format[] = '%s';
+ }
+ if ($merged_note !== '') {
+ $update['note'] = $merged_note;
+ $format[] = '%s';
+ }
- $limit = max(1, min(100, (int) ($args['limit'] ?? 20)));
- $offset = max(0, (int) ($args['offset'] ?? 0));
+ $ok = $wpdb->update($this->table(), $update, ['id' => $withdrawal_id], $format, ['%d']) !== false;
+ return $ok ? true : new WP_Error('aksrt_withdraw_update', __('提现状态更新失败', 'aksrt'));
+ }
- $where_sql = $where ? 'WHERE ' . implode(' AND ', $where) : '';
- $sql = "SELECT * FROM {$this->table()} {$where_sql} ORDER BY created_at DESC LIMIT %d OFFSET %d";
- $params[] = $limit;
- $params[] = $offset;
+ /**
+ * Whether user has open (pending/processing/approved) withdrawals.
+ */
+ public function has_open_withdrawal(int $user_id): bool {
+ return $this->count_open($user_id) > 0;
+ }
- if (!empty($params)) {
- return (array) $wpdb->get_results($wpdb->prepare($sql, $params), ARRAY_A);
- }
- return (array) $wpdb->get_results($sql, ARRAY_A);
- }
+ /**
+ * Count open withdrawals.
+ */
+ public function count_open(int $user_id): int {
+ global $wpdb;
+ if ($user_id <= 0) {
+ return 0;
+ }
+ return (int) $wpdb->get_var($wpdb->prepare(
+ "SELECT COUNT(*) FROM {$this->table()}
+ WHERE user_id = %d AND status IN ('pending','approved','processing')",
+ $user_id
+ ));
+ }
- /**
- * 获取提现总数(支持状态过滤)
- */
- public function get_count(array $args = []): int {
- global $wpdb;
+ /**
+ * Sum of frozen gross amounts still open (balance source).
+ */
+ public function sum_pending_hold(int $user_id): float {
+ global $wpdb;
+ if ($user_id <= 0) {
+ return 0.0;
+ }
+ $rows = $wpdb->get_results($wpdb->prepare(
+ "SELECT amount, account_info FROM {$this->table()}
+ WHERE user_id = %d AND status IN ('pending','approved','processing')",
+ $user_id
+ ), ARRAY_A);
+ $total = 0.0;
+ foreach ((array) $rows as $row) {
+ $meta = $this->parse_account_meta((string) ($row['account_info'] ?? ''));
+ if (!$this->is_balance_source($meta) || !empty($meta['balance_refunded'])) {
+ continue;
+ }
+ $total += $this->resolve_gross_amount($row, $meta);
+ }
+ return round($total, 2);
+ }
- $status = sanitize_key((string) ($args['status'] ?? ''));
- if ($status !== '') {
- return (int) $wpdb->get_var($wpdb->prepare(
- "SELECT COUNT(*) FROM {$this->table()} WHERE status = %s",
- $status
- ));
- }
- return (int) $wpdb->get_var("SELECT COUNT(*) FROM {$this->table()}");
- }
+ /**
+ * 获取用户提现列表
+ *
+ * @param array{status?:string,limit?:int,offset?:int,open_only?:bool} $args
+ * @return list>
+ */
+ public function get_user_withdrawals(int $user_id, array $args = []): array {
+ global $wpdb;
- /**
- * 获取提现统计
- */
- public function get_stats(): array {
- global $wpdb;
+ $where = ['user_id = %d'];
+ $params = [$user_id];
- $row = $wpdb->get_row(
- "SELECT
- COUNT(*) as total,
- SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) as pending,
- SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) as completed,
- COALESCE(SUM(CASE WHEN status = 'completed' THEN amount ELSE 0 END), 0) as total_paid
- FROM {$this->table()}",
- ARRAY_A
- );
+ if (!empty($args['open_only'])) {
+ $where[] = "status IN ('pending','approved','processing')";
+ } else {
+ $status = sanitize_key((string) ($args['status'] ?? ''));
+ if ($status !== '') {
+ $where[] = 'status = %s';
+ $params[] = $status;
+ }
+ }
- return [
- 'total' => (int) ($row['total'] ?? 0),
- 'pending' => (int) ($row['pending'] ?? 0),
- 'completed' => (int) ($row['completed'] ?? 0),
- 'total_paid' => (float) ($row['total_paid'] ?? 0),
- ];
- }
+ $limit = max(1, min(100, (int) ($args['limit'] ?? 20)));
+ $offset = max(0, (int) ($args['offset'] ?? 0));
+
+ $sql = "SELECT * FROM {$this->table()} WHERE " . implode(' AND ', $where) . ' ORDER BY created_at DESC LIMIT %d OFFSET %d';
+ $params[] = $limit;
+ $params[] = $offset;
+
+ $rows = (array) $wpdb->get_results($wpdb->prepare($sql, $params), ARRAY_A);
+ return array_map([$this, 'normalize_row'], $rows);
+ }
+
+ /**
+ * 获取所有提现列表(管理后台)
+ *
+ * @return list>
+ */
+ public function get_all(array $args = []): array {
+ global $wpdb;
+
+ $where = [];
+ $params = [];
+
+ $status = sanitize_key((string) ($args['status'] ?? ''));
+ if ($status !== '') {
+ $where[] = 'status = %s';
+ $params[] = $status;
+ }
+
+ $limit = max(1, min(100, (int) ($args['limit'] ?? 20)));
+ $offset = max(0, (int) ($args['offset'] ?? 0));
+
+ $where_sql = $where ? 'WHERE ' . implode(' AND ', $where) : '';
+ $sql = "SELECT * FROM {$this->table()} {$where_sql} ORDER BY created_at DESC LIMIT %d OFFSET %d";
+ $params[] = $limit;
+ $params[] = $offset;
+
+ if (!empty($params)) {
+ $rows = (array) $wpdb->get_results($wpdb->prepare($sql, $params), ARRAY_A);
+ } else {
+ $rows = (array) $wpdb->get_results($sql, ARRAY_A);
+ }
+ return array_map([$this, 'normalize_row'], $rows);
+ }
+
+ public function get_count(array $args = []): int {
+ global $wpdb;
+
+ $status = sanitize_key((string) ($args['status'] ?? ''));
+ if ($status !== '') {
+ return (int) $wpdb->get_var($wpdb->prepare(
+ "SELECT COUNT(*) FROM {$this->table()} WHERE status = %s",
+ $status
+ ));
+ }
+ return (int) $wpdb->get_var("SELECT COUNT(*) FROM {$this->table()}");
+ }
+
+ public function get_stats(): array {
+ global $wpdb;
+
+ $row = $wpdb->get_row(
+ "SELECT
+ COUNT(*) as total,
+ SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) as pending,
+ SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) as completed,
+ COALESCE(SUM(CASE WHEN status = 'completed' THEN amount ELSE 0 END), 0) as total_paid
+ FROM {$this->table()}",
+ ARRAY_A
+ );
+
+ return [
+ 'total' => (int) ($row['total'] ?? 0),
+ 'pending' => (int) ($row['pending'] ?? 0),
+ 'completed' => (int) ($row['completed'] ?? 0),
+ 'total_paid' => (float) ($row['total_paid'] ?? 0),
+ ];
+ }
+
+ /**
+ * @param array $row
+ * @return array
+ */
+ private function normalize_row(array $row): array {
+ $meta = $this->parse_account_meta((string) ($row['account_info'] ?? ''));
+ $gross = $this->resolve_gross_amount($row, $meta);
+ $row['meta'] = $meta;
+ $row['gross_amount'] = $gross;
+ $row['fee_rate'] = (int) ($meta['fee_rate'] ?? 0);
+ $row['display_account'] = (string) ($meta['account'] ?? $row['account_info'] ?? '');
+ $row['source'] = (string) ($meta['source'] ?? 'balance');
+ return $row;
+ }
+
+ /**
+ * @return array
+ */
+ private function parse_account_meta(string $raw): array {
+ $raw = trim($raw);
+ if ($raw === '') {
+ return [];
+ }
+ $decoded = json_decode($raw, true);
+ return is_array($decoded) ? $decoded : ['account' => $raw];
+ }
+
+ /**
+ * @param array $meta
+ */
+ private function encode_account_meta(array $meta, string $fallback_raw): string {
+ if ($meta === []) {
+ return $fallback_raw;
+ }
+ $json = wp_json_encode($meta, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
+ return is_string($json) ? $json : $fallback_raw;
+ }
+
+ /**
+ * @param array $meta
+ */
+ private function is_balance_source(array $meta): bool {
+ $source = sanitize_key((string) ($meta['source'] ?? 'balance'));
+ // Empty / balance = cash wallet freeze path.
+ return $source === '' || $source === 'balance';
+ }
+
+ /**
+ * @param array $row
+ * @param array $meta
+ */
+ private function resolve_gross_amount(array $row, array $meta): float {
+ if (isset($meta['gross_amount'])) {
+ return max(0, round((float) $meta['gross_amount'], 2));
+ }
+ // Legacy rows: amount was net or gross without meta.
+ return max(0, round((float) ($row['amount'] ?? 0), 2));
+ }
+
+ private function merge_note(string $old, string $new): string {
+ $new = sanitize_textarea_field($new);
+ if ($new === '') {
+ return sanitize_textarea_field($old);
+ }
+ $old = sanitize_textarea_field($old);
+ if ($old === '') {
+ return $new;
+ }
+ return $old . "\n" . $new;
+ }
}
diff --git a/inc/helpers/user-center-renderers/credits-wallet.php b/inc/helpers/user-center-renderers/credits-wallet.php
index 462fe5c7..f33b7d7d 100644
--- a/inc/helpers/user-center-renderers/credits-wallet.php
+++ b/inc/helpers/user-center-renderers/credits-wallet.php
@@ -26,6 +26,14 @@ $exchange_on = !empty($recharge['exchange']['enabled']);
$all_settings = class_exists('AKSRT_Settings') ? AKSRT_Settings::get_all() : [];
$withdraw_on = !empty($all_settings['pay_balance_withdraw_s']);
$withdraw_min = max(1, (float) ($all_settings['pay_balance_withdraw_lowest_money'] ?? $all_settings['pay_rebate_withdraw_lowest_money'] ?? 50));
+$withdraw_pending_hold = 0.0;
+$withdraw_history = [];
+if ($withdraw_on && class_exists('AKSRT_Withdrawal')) {
+ $wd_svc = AKSRT_Withdrawal::get_instance();
+ $withdraw_pending_hold = $wd_svc->sum_pending_hold((int) ($user_id ?? 0));
+ $withdraw_history = $wd_svc->get_user_withdrawals((int) ($user_id ?? 0), ['limit' => 30]);
+}
+$withdraw_processing = $withdraw_pending_hold > 0;
$card_pass_balance = ['enabled' => false, 'desc' => '', 'only_password' => true];
if (class_exists('AKSRT_Charge_Card')) {
$opts = AKSRT_Charge_Card::get_instance()->get_public_options();
@@ -49,6 +57,12 @@ $mark = (string) ($charge_view['currency_mark'] ?? '元');
+
+
+
@@ -60,7 +74,11 @@ $mark = (string) ($charge_view['currency_mark'] ?? '元');
-
+
+
+
+
+
@@ -74,6 +92,12 @@ $mark = (string) ($charge_view['currency_mark'] ?? '元');
+
+
+
@@ -231,6 +255,53 @@ $mark = (string) ($charge_view['currency_mark'] ?? '元');
+
+ __('待处理', 'aksrt'),
+ 'approved' => __('已通过', 'aksrt'),
+ 'processing' => __('处理中', 'aksrt'),
+ 'completed' => __('已完成', 'aksrt'),
+ 'rejected' => __('已拒绝', 'aksrt'),
+ ];
+ ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0.001) : ?>
+ ·
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
|