%4$s', esc_attr($class), $user_id, $post_id, esc_html($label) ); } } if (!function_exists('aksrt_render_rewards_modal_for_user')) { function aksrt_render_rewards_modal_for_user(int $user_id, int $post_id = 0): void { static $done = []; $key = $user_id . ':' . $post_id; if ($user_id <= 0 || isset($done[$key]) || !aksrt_rewards_enabled()) { return; } $done[$key] = true; $settings = \AKSRT\Settings\Settings::get_post_page_settings(); $amounts = (array) ($settings['rewards_amounts'] ?? [5, 10, 20, 50, 100]); $render_amounts = []; foreach ($amounts as $amount) { try { $parsed = \AKSRT\Ledger\AssetAmount::balance(is_string($amount) ? $amount : ''); if ($parsed->minor() > 0) { $render_amounts[] = $parsed->decimal(); } } catch (\InvalidArgumentException $e) { continue; } } $recipient = get_userdata($user_id); ?> __('请先登录后赞赏', 'aksrt')], 401); } $recharge = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_payment_recharge_settings() : []; if (!aksrt_rewards_enabled() || empty($recharge['enabled'])) { wp_send_json_error(['message' => __('赞赏功能当前不可用', 'aksrt')], 400); } $sender_id = get_current_user_id(); $post_id = isset($_POST['post_id']) ? absint($_POST['post_id']) : 0; $recipient_id = isset($_POST['recipient_id']) ? absint($_POST['recipient_id']) : 0; if ($post_id > 0) { $post = get_post($post_id); if (!$post instanceof WP_Post || $post->post_status !== 'publish') { wp_send_json_error(['message' => __('赞赏内容不存在', 'aksrt')], 404); } $recipient_id = (int) $post->post_author; } if ($recipient_id <= 0 || !get_userdata($recipient_id) || $recipient_id === $sender_id) { wp_send_json_error(['message' => __('不能赞赏该用户', 'aksrt')], 400); } $settings = \AKSRT\Settings\Settings::get_post_page_settings(); $raw_amount = isset($_POST['amount']) ? (string) wp_unslash($_POST['amount']) : ''; try { $amount = \AKSRT\Ledger\AssetAmount::balance($raw_amount); $minimum = \AKSRT\Ledger\AssetAmount::balance(is_string($settings['rewards_min'] ?? null) ? $settings['rewards_min'] : ''); $maximum = \AKSRT\Ledger\AssetAmount::balance(is_string($settings['rewards_max'] ?? null) ? $settings['rewards_max'] : ''); } catch (\InvalidArgumentException $e) { wp_send_json_error(['message' => __('赞赏金额格式无效', 'aksrt')], 400); } if ($amount->minor() < $minimum->minor() || $amount->minor() > $maximum->minor()) { wp_send_json_error(['message' => sprintf(__('赞赏金额需在 ¥%1$s 至 ¥%2$s 之间', 'aksrt'), $minimum->formatted(), $maximum->formatted())], 400); } if (empty($settings['rewards_custom'])) { $allowed_amounts = []; foreach ((array) ($settings['rewards_amounts'] ?? []) as $allowed) { try { $allowed_amounts[] = \AKSRT\Ledger\AssetAmount::balance(is_string($allowed) ? $allowed : '')->minor(); } catch (\InvalidArgumentException $e) { continue; } } if (!in_array($amount->minor(), $allowed_amounts, true)) { wp_send_json_error(['message' => __('请选择预设的赞赏金额', 'aksrt')], 400); } } $request_key = sanitize_text_field(wp_unslash((string) ($_SERVER['HTTP_IDEMPOTENCY_KEY'] ?? ''))); if (!wp_is_uuid($request_key)) { wp_send_json_error(['message' => __('缺少或无效的 Idempotency-Key 请求头', 'aksrt')], 400); } $operation_key = \AKSRT\Ledger\OperationKey::deterministic('client-tip', [$sender_id, strtolower($request_key)]); $rate_key = 'aksrt_tip_rate_' . $sender_id; $recent_operation = (string) get_transient($rate_key); if ($recent_operation !== '' && !hash_equals($recent_operation, $operation_key)) { wp_send_json_error(['message' => __('操作太频繁,请稍后再试', 'aksrt')], 429); } set_transient($rate_key, $operation_key, 3); $result = \AKSRT\Ledger\LedgerService::get_instance()->transfer( $sender_id, $recipient_id, \AKSRT\Ledger\AssetType::BALANCE, $amount->minor(), 0, $operation_key, 'tip', ['type' => 'tip', 'id' => $post_id], $post_id > 0 ? sprintf(__('赞赏文章 #%d', 'aksrt'), $post_id) : __('用户赞赏', 'aksrt'), __('收到用户赞赏', 'aksrt') ); if (is_wp_error($result)) { wp_send_json_error(['message' => $result->get_error_message()], (int) ($result->get_error_data()['status'] ?? 400)); } wp_send_json_success([ 'message' => __('赞赏成功,金额已进入对方余额', 'aksrt'), 'transaction_id' => $result->transaction_id, 'replayed' => $result->replayed, 'balance' => \AKSRT\Ledger\AssetAmount::from_minor(\AKSRT\Ledger\AssetType::BALANCE, $result->balance($sender_id, \AKSRT\Ledger\AssetType::BALANCE))->decimal(), ]); });