196 lines
9.9 KiB
PHP
196 lines
9.9 KiB
PHP
<?php
|
|
/**
|
|
* On-site wallet tipping helpers.
|
|
*
|
|
* @package AKSRT
|
|
*/
|
|
|
|
defined('ABSPATH') || exit;
|
|
|
|
if (!function_exists('aksrt_rewards_enabled')) {
|
|
function aksrt_rewards_enabled(): bool {
|
|
if (!class_exists('\AKSRT\Settings\Settings')) {
|
|
return false;
|
|
}
|
|
$settings = \AKSRT\Settings\Settings::get_post_page_settings();
|
|
return !empty($settings['rewards_enabled']);
|
|
}
|
|
}
|
|
|
|
if (!function_exists('aksrt_get_rewards_button')) {
|
|
function aksrt_get_rewards_button(int $user_id, string $class = 'but jb-blue ml10 pw-1em', int $post_id = 0): string {
|
|
if ($user_id <= 0 || !aksrt_rewards_enabled()) {
|
|
return '';
|
|
}
|
|
$settings = \AKSRT\Settings\Settings::get_post_page_settings();
|
|
$label = (string) ($settings['rewards_btn_text'] ?? __('赞赏', 'aksrt'));
|
|
$icon = function_exists('aksrt_icon_markup') ? aksrt_icon_markup('zibsvg-money') : '';
|
|
$template_id = 'aksrt-reward-template-' . $user_id . '-' . $post_id;
|
|
return sprintf(
|
|
'<button type="button" class="%1$s aksrt-reward-open" data-aksrt-reward-open data-template-id="%2$s" data-user-id="%3$d" data-post-id="%4$d">%5$s<text>%6$s</text></button>',
|
|
esc_attr($class),
|
|
esc_attr($template_id),
|
|
$user_id,
|
|
$post_id,
|
|
$icon, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- fixed sprite markup.
|
|
esc_html($label)
|
|
);
|
|
}
|
|
}
|
|
|
|
if (!function_exists('aksrt_render_rewards_template_for_user')) {
|
|
function aksrt_render_rewards_template_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);
|
|
$template_id = 'aksrt-reward-template-' . $user_id . '-' . $post_id;
|
|
$title = (string) ($settings['rewards_text'] ?? __('赞赏', 'aksrt'));
|
|
$money_icon = function_exists('aksrt_icon_markup') ? aksrt_icon_markup('zibsvg-money') : '<i class="fa fas fa-money-bill-wave" aria-hidden="true"></i>';
|
|
$close_icon = function_exists('aksrt_icon_markup') ? aksrt_icon_markup('zibsvg-close', 'ic-close') : '<i class="fa fas fa-times" aria-hidden="true"></i>';
|
|
?>
|
|
<template id="<?php echo esc_attr($template_id); ?>" data-user-id="<?php echo (int) $user_id; ?>" data-post-id="<?php echo (int) $post_id; ?>">
|
|
<div class="modal-colorful-header colorful-bg jb-blue">
|
|
<button type="button" class="close" data-aksrt-modal-close aria-label="<?php esc_attr_e('关闭', 'aksrt'); ?>"><?php echo $close_icon; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- fixed theme icon markup. ?></button>
|
|
<div class="colorful-make"></div>
|
|
<div class="text-center">
|
|
<div class="em2x"><?php echo $money_icon; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- fixed theme icon markup. ?></div>
|
|
<div class="mt10 em12 padding-w10 aksrt-reward__title" role="heading" aria-level="2"><?php echo esc_html($title); ?></div>
|
|
</div>
|
|
</div>
|
|
<div class="modal-body aksrt-reward__body">
|
|
<p class="aksrt-reward__desc muted-2-color em09"><?php printf(esc_html__('赞赏将直接进入 %s 的站内余额', 'aksrt'), esc_html($recipient ? $recipient->display_name : __('作者', 'aksrt'))); ?></p>
|
|
<div class="aksrt-reward__amounts">
|
|
<?php foreach ($render_amounts as $index => $amount) : ?>
|
|
<button type="button" class="aksrt-reward__amount<?php echo $index === 0 ? ' is-active' : ''; ?>" data-reward-amount="<?php echo esc_attr($amount); ?>" aria-pressed="<?php echo $index === 0 ? 'true' : 'false'; ?>"><span class="aksrt-reward__currency">¥</span><span class="aksrt-reward__value"><?php echo esc_html($amount); ?></span></button>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
<?php if (!empty($settings['rewards_custom'])) : ?>
|
|
<label class="aksrt-reward__custom">
|
|
<span class="aksrt-reward__custom-label"><?php esc_html_e('自定义金额', 'aksrt'); ?></span>
|
|
<span class="aksrt-reward__input-wrap"><span class="aksrt-reward__input-currency" aria-hidden="true">¥</span><input class="aksrt-reward__input" type="number" inputmode="decimal" autocomplete="off" min="<?php echo esc_attr((string) ($settings['rewards_min'] ?? 1)); ?>" max="<?php echo esc_attr((string) ($settings['rewards_max'] ?? 1000)); ?>" step="0.01" data-aksrt-reward-custom placeholder="<?php esc_attr_e('输入金额', 'aksrt'); ?>"></span>
|
|
</label>
|
|
<?php endif; ?>
|
|
<?php if (function_exists('aksrt_is_captcha_enabled_for') && aksrt_is_captcha_enabled_for('tip_user') && function_exists('aksrt_render_captcha')) : ?>
|
|
<div class="aksrt-reward__captcha"><?php aksrt_render_captcha('tip_user'); ?></div>
|
|
<?php endif; ?>
|
|
<button type="button" class="but jb-blue btn-block aksrt-reward__submit" data-aksrt-reward-submit><?php esc_html_e('余额赞赏', 'aksrt'); ?></button>
|
|
<div class="aksrt-reward__message" data-aksrt-reward-message role="status" aria-live="polite" aria-atomic="true"></div>
|
|
</div>
|
|
</template>
|
|
<?php
|
|
}
|
|
}
|
|
|
|
add_action('wp_ajax_aksrt_submit_tip', function (): void {
|
|
check_ajax_referer('aksrt_public_nonce', 'nonce');
|
|
if (!is_user_logged_in()) {
|
|
wp_send_json_error(['message' => __('请先登录后赞赏', '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);
|
|
}
|
|
$captcha_result = aksrt_require_captcha('tip_user', wp_unslash($_POST));
|
|
if ($captcha_result !== true) {
|
|
$captcha_error = is_wp_error($captcha_result) ? $captcha_result : null;
|
|
$captcha_data = $captcha_error ? $captcha_error->get_error_data() : null;
|
|
$captcha_status = is_array($captcha_data) ? (int) ($captcha_data['status'] ?? 400) : 400;
|
|
wp_send_json_error([
|
|
'code' => $captcha_error ? ($captcha_error->get_error_code() ?: 'aksrt_captcha_failed') : 'aksrt_captcha_failed',
|
|
'message' => $captcha_error ? ($captcha_error->get_error_message() ?: __('人机验证失败,请重试', 'aksrt')) : __('人机验证失败,请重试', 'aksrt'),
|
|
], $captcha_status > 0 ? $captcha_status : 400);
|
|
}
|
|
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(),
|
|
]);
|
|
});
|