fix(users): restore WeChat template notification flow

This commit is contained in:
kate522 committed 2026-11-10 20:54:24 +08:00
1 parent 30ceab4615
commit 7fe26d3fff
18 files changed
+1051 -763

No files matched your search

+57 -35
View File
@@ -256,6 +256,12 @@ class UserController extends ControllerBase {
'permission_callback' => [$this, 'check_permission'],
]);
register_rest_route(self::API_NAMESPACE, '/wechat/test', [
'methods' => 'POST',
'callback' => [$this, 'test_wechat_template'],
'permission_callback' => [$this, 'check_permission'],
]);
// ── User Restrictions / Ban ──
// List restrictions for a user (admin)
@@ -324,11 +330,7 @@ class UserController extends ControllerBase {
}
/**
* GET /wechat/status — 微信模板消息配置状态。
*
* 状态形状与 WeChatNotify::get_status() 对齐(该方法的既定用途即 REST 与 AJAX 复用)。
*/
/** GET /wechat/status */
public function get_wechat_status(\WP_REST_Request $request): \WP_REST_Response {
if (!class_exists('\Jio\Payment\WeChatNotify')) {
return $this->error(__('微信通知组件未加载', 'jio'), 500);
@@ -337,39 +339,59 @@ class UserController extends ControllerBase {
return $this->success(['data' => \Jio\Payment\WeChatNotify::get_instance()->get_status()]);
}
/**
* POST /wechat/settings — 更新微信模板消息配置。
*
* 前端在密钥留空且已配置时发送掩码 ***,此时保留已存密钥;
* 未提供的字段沿用当前值。键白名单由 Integration::update_wechat_settings() 收敛。
*/
/** POST /wechat/settings */
public function update_wechat_settings(\WP_REST_Request $request): \WP_REST_Response {
$params = $this->get_json_params($request);
$current = \Jio\Settings\Settings::get_wechat_settings();
$enabled = array_key_exists('enabled', $params) ? !empty($params['enabled']) : !empty($current['enabled']);
$app_id = array_key_exists('app_id', $params)
? sanitize_text_field((string) $params['app_id'])
: (string) ($current['app_id'] ?? '');
$template_id = array_key_exists('template_id', $params)
? sanitize_text_field((string) $params['template_id'])
: (string) ($current['template_id'] ?? '');
// 掩码 *** 表示「不修改密钥」;仅在显式传入其他值时才覆盖。
if (array_key_exists('app_secret', $params) && (string) $params['app_secret'] !== '***') {
$app_secret = sanitize_text_field((string) $params['app_secret']);
} else {
$app_secret = (string) ($current['app_secret'] ?? '');
$params = $this->get_json_params($request);
if (!isset($params['templates']) || !is_array($params['templates'])) {
return $this->error(__('模板配置格式无效', 'jio'), 400);
}
foreach ($params['templates'] as $type => $item) {
if (!isset(\Jio\Settings\WechatTemplates::definitions()[$type]) || !is_array($item)) {
return $this->error(__('通知类型无效', 'jio'), 400);
}
if (isset($item['id']) && !is_string($item['id'])) {
return $this->error(__('模板 ID 格式无效', 'jio'), 400);
}
if (isset($item['keys']) && !is_array($item['keys'])) {
return $this->error(__('模板参数格式无效', 'jio'), 400);
}
foreach (($item['keys'] ?? []) as $field => $key) {
if (!isset(\Jio\Settings\WechatTemplates::definitions()[$type]['fields'][$field])
|| !is_string($key)
|| ($key !== '' && !preg_match('/^[a-zA-Z][a-zA-Z0-9_]{0,63}$/', $key))) {
return $this->error(__('模板参数名无效', 'jio'), 400);
}
}
}
$config = \Jio\Settings\WechatTemplates::normalize($params);
foreach ($config['templates'] as $item) {
$used_keys = [];
$index = 1;
foreach ($item['keys'] as $key) {
$effective_key = $key !== '' ? $key : 'keyword' . $index;
if (isset($used_keys[$effective_key])) {
return $this->error(__('同一模板的参数名不能重复', 'jio'), 400);
}
$used_keys[$effective_key] = true;
$index++;
}
}
\Jio\Settings\Settings::update_wechat_settings($config);
return $this->success(['data' => $config, 'message' => __('微信模板消息设置已保存', 'jio')]);
}
\Jio\Settings\Settings::update_wechat_settings([
'enabled' => $enabled,
'app_id' => $app_id,
'app_secret' => $app_secret,
'template_id' => $template_id,
]);
return $this->success(['message' => __('微信通知配置已保存', 'jio')]);
/** POST /wechat/test */
public function test_wechat_template(\WP_REST_Request $request): \WP_REST_Response {
$params = $this->get_json_params($request);
$type = sanitize_key((string) ($params['type'] ?? ''));
if (!isset(\Jio\Settings\WechatTemplates::definitions()[$type])) {
return $this->error(__('请选择需要测试的模板', 'jio'), 400);
}
$result = \Jio\Payment\WeChatNotify::get_instance()->test_send(get_current_user_id(), $type);
if (is_wp_error($result)) {
return $this->error($result->get_error_message(), (int) ($result->get_error_data()['status'] ?? 400));
}
return $this->success(['message' => __('测试消息已发送', 'jio')]);
}
public function rest_handle_credit_settings(\WP_REST_Request $request): \WP_REST_Response {
@@ -229,6 +229,14 @@ trait UserControllerReportOperations {
return $this->error(__('处理失败', 'jio'));
}
if ($updated > 0 && $status !== (string) ($row['status'] ?? '')) {
do_action('jio_report_status_changed', $report_id, (int) ($row['reporter_id'] ?? 0), $status, $note, [
'category' => (string) ($row['category'] ?? ''),
'reason' => (string) ($row['reason'] ?? ''),
'created_at' => (string) ($row['created_at'] ?? ''),
]);
}
return $this->success([
'message' => __('举报工单已处理', 'jio'),
]);
+10
View File
@@ -90,6 +90,16 @@ final class WeixinGzh {
return false;
}
public function access_token(): string {
$config = self::settings();
$appid = trim((string) ($config['appid'] ?? ''));
$secret = trim((string) ($config['appsecret'] ?? ''));
if ($appid === '' || $secret === '') {
return '';
}
return $this->get_access_token($appid, $secret);
}
public function rest_qrcode_login(\WP_REST_Request $request) {
$limited = $this->rate_limit('qr-create', 12, MINUTE_IN_SECONDS, 2);
if ($limited) {
+1
View File
@@ -77,6 +77,7 @@ final class ApplicationService {
'actor_user_id' => $user_id,
'summary' => ['name' => $name],
]);
do_action('jio_auth_application_submitted', $user_id, $created);
if (class_exists('Jio\\Settings\\MailNotifications')) {
\Jio\Settings\MailNotifications::auth_submitted($user_id, $created);
}
+7 -3
View File
@@ -179,7 +179,7 @@ class ReportCenter {
global $wpdb;
$existing = $wpdb->get_row($wpdb->prepare(
"SELECT id, reporter_id, notes_json FROM {$table} WHERE id = %d",
"SELECT id, reporter_id, notes_json, category, reason, created_at FROM {$table} WHERE id = %d",
$report_id
));
@@ -205,9 +205,13 @@ class ReportCenter {
}
$result = $wpdb->update($table, $update_data, ['id' => $report_id], $format, ['%d']);
if ($result !== false) {
if ($result > 0) {
// 第 4 参携带处理说明:邮件通知(report_result)与站内信均需反馈给举报人。
do_action('jio_report_status_changed', $report_id, (int) ($existing->reporter_id ?? 0), $new_status, $note_clean);
do_action('jio_report_status_changed', $report_id, (int) ($existing->reporter_id ?? 0), $new_status, $note_clean, [
'category' => (string) ($existing->category ?? ''),
'reason' => (string) ($existing->reason ?? ''),
'created_at' => (string) ($existing->created_at ?? ''),
]);
$this->notify_reporter_status_changed((int) ($existing->reporter_id ?? 0), $new_status, $note_clean);
}
return $result !== false;
+137 -262
View File
@@ -5,278 +5,153 @@
namespace Jio\Payment;
use Jio\Auth\WeixinGzh;
use Jio\Settings\Settings;
use Jio\Settings\WechatTemplates;
defined('ABSPATH') || exit;
class WeChatNotify {
final class WeChatNotify {
use WeChatNotifyEvents;
private static $instance = null;
private static $instance = null;
/**
* 可联动微信模板消息的站内通知类型。
* report:举报处理结果;system:禁封/限制/解除等账号限制通知。
*/
const NOTIFY_TYPES = ['comment_reply', 'system_notice', 'order_paid', 'credits_change', 'report', 'system'];
public static function get_instance(): self {
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
public static function get_instance(): self {
if (null === self::$instance) self::$instance = new self();
return self::$instance;
}
public function init(): void {
$this->register_event_hooks();
}
public function init(): void {
add_action('jio_notification_sent', [$this, 'maybe_send_wechat'], 10, 3);
add_action('wp_ajax_jio_wechat_status', [$this, 'ajax_status']);
}
public function get_status(): array {
$config = Settings::get_wechat_settings();
global $wpdb;
$bound_users = (int) $wpdb->get_var(
"SELECT COUNT(DISTINCT user_id) FROM {$wpdb->usermeta} WHERE meta_key = 'jio_weixingzh_id' AND meta_value != ''"
);
return [
'config' => $config,
'types' => WechatTemplates::definitions(),
'oauth_ready' => $this->oauth_ready(),
'bound_users' => $bound_users,
'current_user_bound' => trim((string) get_user_meta(get_current_user_id(), 'jio_weixingzh_id', true)) !== '',
];
}
/**
* 获取微信通知配置
*/
public function get_config(): array {
if (class_exists('\Jio\Settings\Settings')) {
return \Jio\Settings\Settings::get_wechat_settings();
}
return ['enabled' => false];
}
public function test_send(int $user_id, string $type) {
$definition = WechatTemplates::definitions()[$type] ?? null;
if ($definition === null) {
return new \WP_Error('jio_wechat_type', __('通知类型无效', 'jio'), ['status' => 400]);
}
$values = [];
foreach ($definition['fields'] as $field => $label) {
$values[$field] = $this->sample_value($field, $label);
}
return $this->deliver($user_id, $type, $values, home_url('/'));
}
/**
* 发送微信模板消息
*
* @param int $user_id 用户 ID
* @param string $template 模板标识
* @param array $data 模板数据
* @return bool
*/
public function send(int $user_id, string $template, array $data = []): bool {
$config = $this->get_config();
public function send_event(int $user_id, string $type, array $values, string $url = ''): bool {
return $this->deliver($user_id, $type, $values, $url) === true;
}
if (empty($config['enabled'])) {
return false;
}
private function deliver(int $user_id, string $type, array $values, string $url) {
$config = Settings::get_wechat_settings();
if (empty($config['enabled'])) {
return new \WP_Error('jio_wechat_disabled', __('请先启用微信模板消息并保存设置', 'jio'), ['status' => 400]);
}
$template = $config['templates'][$type] ?? null;
if (!is_array($template) || empty($template['enabled']) || empty($template['id'])) {
return new \WP_Error('jio_wechat_template', __('该通知类型未启用或未填写模板 ID', 'jio'), ['status' => 400]);
}
if (!$this->oauth_ready()) {
return new \WP_Error('jio_wechat_oauth', __('请先启用并配置微信公众号登录服务号', 'jio'), ['status' => 400]);
}
$openid = trim((string) get_user_meta($user_id, 'jio_weixingzh_id', true));
if ($openid === '') {
return new \WP_Error('jio_wechat_unbound', __('接收账号尚未绑定微信公众号', 'jio'), ['status' => 400]);
}
try {
$token = WeixinGzh::get_instance()->access_token();
} catch (\Throwable $error) {
return new \WP_Error('jio_wechat_token', __('获取公众号访问令牌失败:', 'jio') . $error->getMessage(), ['status' => 502]);
}
if ($token === '') {
return new \WP_Error('jio_wechat_token', __('无法获取公众号访问令牌', 'jio'), ['status' => 502]);
}
$data = $this->template_data($type, $values, $template['keys']);
if ($data === []) {
return new \WP_Error('jio_wechat_data', __('模板消息缺少参数', 'jio'), ['status' => 400]);
}
$response = wp_remote_post('https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=' . rawurlencode($token), [
'headers' => ['Content-Type' => 'application/json'],
'body' => wp_json_encode([
'touser' => $openid,
'template_id' => $template['id'],
'url' => esc_url_raw($url !== '' ? $url : home_url('/')),
'data' => $data,
]),
'timeout' => 10,
]);
if (is_wp_error($response)) {
return new \WP_Error('jio_wechat_http', $response->get_error_message(), ['status' => 502]);
}
$body = json_decode(wp_remote_retrieve_body($response), true);
if (wp_remote_retrieve_response_code($response) !== 200 || !is_array($body) || !array_key_exists('errcode', $body)) {
return new \WP_Error('jio_wechat_response', __('公众号返回了无效响应', 'jio'), ['status' => 502]);
}
if ((int) $body['errcode'] !== 0) {
return new \WP_Error('jio_wechat_api', sprintf(__('公众号发送失败(%1$s):%2$s', 'jio'), (string) $body['errcode'], (string) ($body['errmsg'] ?? '')), ['status' => 502]);
}
return true;
}
// 获取用户的微信 openid
$openid = get_user_meta($user_id, 'jio_wechat_openid', true);
if (empty($openid)) {
return false;
}
private function oauth_ready(): bool {
$config = Settings::get_weixingzh_settings();
return WeixinGzh::is_enabled()
&& (string) ($config['gzh_type'] ?? '') === 'service'
&& trim((string) ($config['appid'] ?? '')) !== ''
&& trim((string) ($config['appsecret'] ?? '')) !== '';
}
// 获取 access token
$access_token = $this->get_access_token($config);
if (empty($access_token)) {
if (defined('WP_DEBUG') && WP_DEBUG) {
error_log('[Jio WeChat] Failed to get access token');
}
return false;
}
private function template_data(string $type, array $values, array $keys): array {
$definition = WechatTemplates::definitions()[$type] ?? null;
if ($definition === null) {
return [];
}
$data = [];
$index = 1;
foreach ($definition['fields'] as $field => $label) {
$value = sanitize_text_field((string) ($values[$field] ?? ''));
$key = (string) ($keys[$field] ?? '');
if ($key === '') {
$key = 'keyword' . $index;
}
if (str_contains($key, 'thing') && mb_strlen($value, 'UTF-8') > 19) {
$value = mb_substr($value, 0, 16, 'UTF-8') . '...';
}
$data[$key] = ['value' => $value];
$index++;
}
return $data;
}
// 构建模板消息
$template_id = $config['template_id'] ?? '';
if (empty($template_id)) {
if (defined('WP_DEBUG') && WP_DEBUG) {
error_log('[Jio WeChat] No template ID configured');
}
return false;
}
$message = $this->build_template_message($template, $data, $openid, $template_id);
// 调用微信模板消息 API
$response = wp_remote_post('https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=' . $access_token, [
'headers' => ['Content-Type' => 'application/json'],
'body' => wp_json_encode($message),
'timeout' => 10,
]);
if (is_wp_error($response)) {
if (defined('WP_DEBUG') && WP_DEBUG) {
error_log('[Jio WeChat] API error: ' . $response->get_error_message());
}
return false;
}
$body = json_decode(wp_remote_retrieve_body($response), true);
$errcode = (int) ($body['errcode'] ?? 0);
if (defined('WP_DEBUG') && WP_DEBUG) {
error_log(sprintf('[Jio WeChat] User: %d, Template: %s, Result: %s', $user_id, $template, wp_json_encode($body)));
}
return $errcode === 0;
}
/**
* 获取微信 access token(带缓存)
*/
private function get_access_token(array $config): string {
$cache_key = 'jio_wechat_token';
$cached = get_transient($cache_key);
if (!empty($cached)) return (string) $cached;
$app_id = $config['app_id'] ?? '';
$app_secret = $config['app_secret'] ?? '';
if (empty($app_id) || empty($app_secret)) return '';
$response = wp_remote_get(sprintf(
'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s',
urlencode($app_id),
urlencode($app_secret)
));
if (is_wp_error($response)) return '';
$body = json_decode(wp_remote_retrieve_body($response), true);
$token = $body['access_token'] ?? '';
$expires = max(600, (int) ($body['expires_in'] ?? 7200) - 300);
if (!empty($token)) {
set_transient($cache_key, $token, $expires);
}
return (string) $token;
}
/**
* 构建模板消息体
*/
private function build_template_message(string $template, array $data, string $openid, string $template_id): array {
$url = $data['url'] ?? home_url('/');
$values = $data['values'] ?? [];
// 通用兜底:站内通知未带模板字段时,用标题/内容合成。
if (empty($values)) {
$values = array_filter([
'first' => (string) ($data['title'] ?? ''),
'keyword1' => (string) ($data['description'] ?? ''),
], static function ($v) { return $v !== ''; });
}
$template_data = [];
foreach ($values as $key => $value) {
$template_data[$key] = [
'value' => (string) $value,
'color' => '#333333',
];
}
return [
'touser' => $openid,
'template_id' => $template_id,
'url' => esc_url($url),
'data' => $template_data,
];
}
/**
* 绑定用户微信 openid
*/
public function bind_user(int $user_id, string $openid): bool {
if ($user_id <= 0 || empty($openid)) return false;
return (bool) update_user_meta($user_id, 'jio_wechat_openid', sanitize_text_field($openid));
}
/**
* 解绑用户微信
*/
public function unbind_user(int $user_id): bool {
return (bool) delete_user_meta($user_id, 'jio_wechat_openid');
}
/**
* 监听站内通知,联动微信通知
*/
public function maybe_send_wechat(int $user_id, string $type, array $data): void {
$config = $this->get_config();
if (empty($config['enabled'])) return;
// 仅特定类型通知发送微信(含举报处理结果与账号限制通知)
if (!in_array($type, self::NOTIFY_TYPES, true)) return;
$this->send($user_id, $type, $data);
}
/**
* AJAX: Get WeChat notification status (admin only).
*/
public function ajax_status(): void {
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => __('权限不足', 'jio')]);
}
wp_send_json_success($this->get_status());
}
/**
* Get WeChat service status (used by REST and AJAX).
*/
public function get_status(): array {
$config = $this->get_config();
$has_token = false;
if (!empty($config['app_id']) && !empty($config['app_secret'])) {
$cached = get_transient('jio_wechat_token');
$has_token = !empty($cached);
}
// Count bound users
global $wpdb;
$bound_count = 0;
if ($wpdb) {
$bound_count = (int) $wpdb->get_var(
"SELECT COUNT(*) FROM {$wpdb->usermeta} WHERE meta_key = 'jio_wechat_openid' AND meta_value != ''"
);
}
return [
'enabled' => !empty($config['enabled']),
'appid' => $config['app_id'] ?? '',
'configured' => !empty($config['app_id']) && !empty($config['app_secret']),
'has_token' => $has_token,
'has_template' => !empty($config['template_id']),
'bound_users' => $bound_count,
'app_secret' => !empty($config['app_secret']) ? '***' : '',
'allowed_types' => self::NOTIFY_TYPES,
];
}
/**
* Get notification subscriptions for a user.
*/
public function get_user_subscriptions(int $user_id): array {
$subs = get_user_meta($user_id, 'jio_wechat_subscriptions', true);
if (!is_array($subs)) {
$subs = [
'comment_reply' => true,
'system_notice' => true,
'order_paid' => true,
'credits_change' => false,
'report' => true,
'system' => true,
];
}
return $subs;
}
/**
* Update notification subscriptions for a user.
*/
public function update_user_subscriptions(int $user_id, array $subscriptions): bool {
$allowed = self::NOTIFY_TYPES;
$clean = [];
foreach ($allowed as $key) {
$clean[$key] = !empty($subscriptions[$key]);
}
return (bool) update_user_meta($user_id, 'jio_wechat_subscriptions', $clean);
}
/**
* Check if a user should receive a specific notification type via WeChat.
*/
public function should_notify(int $user_id, string $type): bool {
$config = $this->get_config();
if (empty($config['enabled'])) return false;
$openid = get_user_meta($user_id, 'jio_wechat_openid', true);
if (empty($openid)) return false;
$subs = $this->get_user_subscriptions($user_id);
return !empty($subs[$type]);
}
private function sample_value(string $field, string $label): string {
if (str_contains($field, 'time')) {
return current_time('Y-m-d H:i:s');
}
if (str_contains($field, 'price')) {
return '88.50';
}
if ($field === 'num') {
return '2026010100000000000001';
}
if ($field === 'status') {
return '已完成';
}
return '测试' . $label;
}
}
+246
View File
@@ -0,0 +1,246 @@
<?php
/**
* @package Jio
*/
namespace Jio\Payment;
defined('ABSPATH') || exit;
trait WeChatNotifyEvents {
private bool $event_hooks_registered = false;
private function register_event_hooks(): void {
if ($this->event_hooks_registered) {
return;
}
$this->event_hooks_registered = true;
add_action('jio_order_paid', [$this, 'on_wechat_order_paid'], 20, 2);
add_action('jio_asset_transaction_committed', [$this, 'on_wechat_asset_committed'], 20, 2);
add_action('jio_withdrawal_submitted', [$this, 'on_wechat_withdrawal_submitted'], 20, 2);
add_action('jio_withdrawal_status_changed', [$this, 'on_wechat_withdrawal_processed'], 20, 4);
add_action('jio_auth_application_submitted', [$this, 'on_wechat_auth_submitted'], 20, 2);
add_action('jio_auth_application_reviewed', [$this, 'on_wechat_auth_reviewed'], 20, 4);
add_action('jio_report_submitted', [$this, 'on_wechat_report_submitted'], 20, 2);
add_action('jio_report_status_changed', [$this, 'on_wechat_report_processed'], 20, 5);
add_action('jio_user_phone_changed', [$this, 'on_wechat_phone_changed'], 20, 3);
add_action('jio_user_email_changed', [$this, 'on_wechat_email_changed'], 20, 3);
add_action('jio_wechat_comment_postauthor', [$this, 'on_wechat_comment_postauthor'], 20, 2);
add_action('jio_wechat_comment_parent', [$this, 'on_wechat_comment_parent'], 20, 2);
}
public function on_wechat_order_paid(string $order_number, array $order): void {
$user_id = (int) ($order['user_id'] ?? 0);
$product_id = (int) ($order['product_id'] ?? 0);
$product = $product_id > 0 ? (string) get_the_title($product_id) : '';
if ($product === '') {
$product = (string) ($order['product_type'] ?? __('订单', 'jio'));
}
$currency = (string) ($order['currency'] ?? 'CNY');
$price = (string) ($order['amount'] ?? '0');
if ($currency === 'points') {
$price .= ' ' . __('积分', 'jio');
} else {
$price .= ' ' . $currency;
}
$time = (string) ($order['paid_at'] ?? current_time('mysql'));
$user_url = $this->wechat_user_url('orders');
$this->send_event($user_id, 'payment_order', [
'name' => $product,
'price' => $price,
'time' => $time,
'num' => $order_number,
], $user_url);
$this->send_wechat_admins('payment_order_admin', [
'name' => $product,
'price' => $price,
'user' => $this->wechat_user_name($user_id),
'time' => $time,
'num' => $order_number,
], admin_url('admin.php?page=jio_orders&order=' . rawurlencode($order_number)));
}
public function on_wechat_asset_committed($result, $command): void {
if (!$result instanceof \Jio\Ledger\LedgerResult || !$command instanceof \Jio\Ledger\LedgerCommand || $result->replayed) {
return;
}
$type = match ($command->reason) {
'creator_income' => 'payment_order_to_income',
'affiliate_commission' => 'payment_order_to_referrer',
default => '',
};
if ($type === '') {
return;
}
$order_number = $this->wechat_ledger_order_number($command);
if ($order_number === '') {
return;
}
$sent = [];
foreach ($result->entries as $entry) {
$user_id = (int) ($entry['user_id'] ?? 0);
$minor = (int) ($entry['delta_minor'] ?? 0);
if ($user_id <= 0 || $minor <= 0 || isset($sent[$user_id])) {
continue;
}
$sent[$user_id] = true;
$asset = (string) ($entry['asset'] ?? 'balance');
try {
$price = \Jio\Ledger\AssetAmount::from_minor($asset, $minor)->decimal();
} catch (\InvalidArgumentException | \ValueError $e) {
continue;
}
$price .= $asset === 'points' ? ' ' . __('积分', 'jio') : ' CNY';
$this->send_event($user_id, $type, [
'name' => $type === 'payment_order_to_income' ? __('创作收益', 'jio') : __('推荐佣金', 'jio'),
'price' => $price,
'time' => current_time('mysql'),
'num' => $order_number,
], $this->wechat_user_url('credits'));
}
}
public function on_wechat_withdrawal_submitted(int $user_id, array $row): void {
$this->send_wechat_admins('apply_withdraw_admin', [
'user' => $this->wechat_user_name($user_id),
'price' => (string) ($row['gross_amount'] ?? '0') . ' CNY',
'time' => (string) ($row['created_at'] ?? current_time('mysql')),
], admin_url('admin.php?page=jio_withdraw&status=pending'));
}
public function on_wechat_withdrawal_processed(int $user_id, array $row, string $status, string $note): void {
if (!in_array($status, [Withdrawal::STATUS_COMPLETED, Withdrawal::STATUS_REJECTED], true)) {
return;
}
$gross = (string) ($row['gross_amount'] ?? '0');
$received = $status === Withdrawal::STATUS_COMPLETED ? (string) ($row['amount'] ?? '0') : '0.00';
$fee = '0.00';
try {
$gross_minor = \Jio\Ledger\AssetAmount::balance($gross)->minor();
$received_minor = \Jio\Ledger\AssetAmount::balance((string) ($row['amount'] ?? '0'))->minor();
$fee = \Jio\Ledger\AssetAmount::from_minor('balance', max(0, $gross_minor - $received_minor))->decimal();
} catch (\InvalidArgumentException $e) {
// Keep the displayed amount available if a historical row is malformed.
}
$this->send_event($user_id, 'withdraw_process', [
'create_time' => (string) ($row['created_at'] ?? ''),
'process_time' => current_time('mysql'),
'status' => $status === Withdrawal::STATUS_COMPLETED ? __('已完成', 'jio') : __('已拒绝', 'jio'),
'price' => $gross . ' CNY',
'received_price' => $received . ' CNY',
'service_price' => $fee . ' CNY',
], $this->wechat_user_url('credits'));
}
public function on_wechat_auth_submitted(int $user_id, array $application): void {
$this->send_wechat_admins('auth_apply_admin', [
'name' => (string) ($application['public_name'] ?? ''),
'time' => (string) ($application['created_at'] ?? current_time('mysql')),
'user' => $this->wechat_user_name($user_id),
], admin_url('users.php?page=jio_user_auth'));
}
public function on_wechat_auth_reviewed(int $user_id, string $status, string $note, array $application): void {
$this->send_event($user_id, 'auth_apply_process', [
'status' => ($status === 'verified' ? __('已通过', 'jio') : __('未通过', 'jio')) . ($note !== '' ? ':' . $note : ''),
'name' => (string) ($application['public_name'] ?? ''),
'desc' => (string) ($application['description'] ?? ''),
'time' => current_time('mysql'),
], function_exists('jio_get_user_center_tab_url') ? jio_get_user_center_tab_url('auth') : $this->wechat_user_url('settings'));
}
public function on_wechat_report_submitted(int $report_id, array $report): void {
$target_id = (int) ($report['target_id'] ?? 0);
$target_type = (string) ($report['target_type'] ?? '');
$user_id = match ($target_type) {
'user' => $target_id,
'post' => (int) (get_post($target_id)->post_author ?? 0),
'comment' => (int) (get_comment($target_id)->user_id ?? 0),
default => 0,
};
$this->send_wechat_admins('report_user_admin', [
'user' => $user_id > 0 ? $this->wechat_user_name($user_id) : __('未知用户', 'jio'),
'time' => current_time('mysql'),
'reason' => (string) ($report['category'] ?? ''),
'desc' => (string) ($report['reason'] ?? ''),
], admin_url('users.php?page=jio_user_ban&type=report&status=pending'));
}
public function on_wechat_report_processed(int $report_id, int $reporter_id, string $status, string $note, array $report = []): void {
if (!in_array($status, ['resolved', 'dismissed'], true)) {
return;
}
$this->send_event($reporter_id, 'report_process', [
'reason' => (string) ($report['category'] ?? $report['reason'] ?? ''),
'time' => (string) ($report['created_at'] ?? ''),
'desc' => $note !== '' ? $note : ($status === 'resolved' ? __('举报已处理', 'jio') : __('举报未通过', 'jio')),
], function_exists('jio_get_messages_page_url') ? (jio_get_messages_page_url('system') ?: home_url('/')) : home_url('/'));
}
public function on_wechat_phone_changed(int $user_id, string $phone, string $old_phone): void {
$this->send_event($user_id, 'bind_phone', [
'name' => $this->wechat_user_name($user_id),
'time' => current_time('mysql'),
'num' => $phone,
], $this->wechat_user_url('settings'));
}
public function on_wechat_email_changed(int $user_id, string $email, string $old_email): void {
$this->send_event($user_id, 'bind_email', [
'name' => $this->wechat_user_name($user_id),
'time' => current_time('mysql'),
'num' => $email,
], $this->wechat_user_url('settings'));
}
public function on_wechat_comment_postauthor(int $user_id, \WP_Comment $comment): void {
$this->send_event($user_id, 'comment_to_postauthor', [
'name' => get_comment_author($comment),
'time' => (string) $comment->comment_date,
'content' => (string) $comment->comment_content,
'post' => get_the_title((int) $comment->comment_post_ID),
], get_comment_link($comment));
}
public function on_wechat_comment_parent(int $user_id, \WP_Comment $comment): void {
$this->send_event($user_id, 'comment_to_parent', [
'name' => get_comment_author($comment),
'time' => (string) $comment->comment_date,
'content' => (string) $comment->comment_content,
], get_comment_link($comment));
}
private function send_wechat_admins(string $type, array $values, string $url): void {
foreach (get_users(['role' => 'administrator', 'fields' => 'ID']) as $user_id) {
$this->send_event((int) $user_id, $type, $values, $url);
}
}
private function wechat_user_name(int $user_id): string {
$user = get_userdata($user_id);
return $user instanceof \WP_User ? (string) $user->display_name : __('用户', 'jio');
}
private function wechat_user_url(string $tab): string {
return function_exists('jio_get_user_center_url') ? jio_get_user_center_url($tab) : home_url('/');
}
private function wechat_ledger_order_number(\Jio\Ledger\LedgerCommand $command): string {
$order_number = (string) ($command->metadata['order_number'] ?? '');
if ($order_number !== '' && $order_number !== $command->ref_key) {
return $order_number;
}
$table_name = $command->reason === 'creator_income' ? 'revenue' : 'commission_ledger';
if (!class_exists('\Jio\Core\DBMigrator') || !ctype_digit($command->ref_key)) {
return '';
}
$table = \Jio\Core\DBMigrator::table($table_name);
if ($table === '') {
return '';
}
global $wpdb;
return (string) $wpdb->get_var($wpdb->prepare("SELECT order_number FROM {$table} WHERE id = %d", (int) $command->ref_key));
}
}
+4 -16
View File
@@ -269,7 +269,7 @@ class Integration {
/**
* 获取微信通知设置
*
* @return array{enabled:bool,app_id:string,app_secret:string,template_id:string}
* @return array{enabled:bool,templates:array}
*/
public static function get_wechat_settings(): array {
$stored = get_option('jio_wechat_settings', []);
@@ -277,28 +277,16 @@ class Integration {
$stored = [];
}
return [
'enabled' => !empty($stored['enabled']),
'app_id' => sanitize_text_field((string) ($stored['app_id'] ?? '')),
'app_secret' => (string) ($stored['app_secret'] ?? ''),
'template_id' => sanitize_text_field((string) ($stored['template_id'] ?? '')),
];
return WechatTemplates::normalize($stored);
}
/**
* 更新微信通知设置
*
* @param array $settings 合并后的完整微信配置
* @param array $settings 完整微信模板配置
*/
public static function update_wechat_settings(array $settings): void {
$allowed = ['enabled', 'app_id', 'app_secret', 'template_id'];
$clean = [];
foreach ($allowed as $key) {
if (array_key_exists($key, $settings)) {
$clean[$key] = $settings[$key];
}
}
update_option('jio_wechat_settings', $clean);
update_option('jio_wechat_settings', WechatTemplates::normalize($settings));
}
}
+53
View File
@@ -0,0 +1,53 @@
<?php
/**
* @package Jio
*/
namespace Jio\Settings;
defined('ABSPATH') || exit;
final class WechatTemplates {
/** @return array<string,array{label:string,group:string,fields:array<string,string>}> */
public static function definitions(): array {
return [
'payment_order' => ['label' => '新订单通知用户', 'group' => '订单与收益', 'fields' => ['name' => '商品名称', 'price' => '金额', 'time' => '付款时间', 'num' => '订单号']],
'payment_order_admin' => ['label' => '新订单通知管理员', 'group' => '订单与收益', 'fields' => ['name' => '商品名称', 'price' => '金额', 'user' => '购买用户', 'time' => '时间', 'num' => '订单号']],
'payment_order_to_income' => ['label' => '获得创作分成通知作者', 'group' => '订单与收益', 'fields' => ['name' => '收入内容', 'price' => '收入金额', 'time' => '时间', 'num' => '订单号']],
'payment_order_to_referrer' => ['label' => '获得推荐佣金通知推荐人', 'group' => '订单与收益', 'fields' => ['name' => '推广内容', 'price' => '收入金额', 'time' => '时间', 'num' => '订单号']],
'apply_withdraw_admin' => ['label' => '用户申请提现通知管理员', 'group' => '提现', 'fields' => ['user' => '申请用户', 'price' => '申请金额', 'time' => '时间']],
'withdraw_process' => ['label' => '提现处理后通知用户', 'group' => '提现', 'fields' => ['create_time' => '申请时间', 'process_time' => '处理时间', 'status' => '处理结果', 'price' => '申请金额', 'received_price' => '到账金额', 'service_price' => '手续费']],
'auth_apply_admin' => ['label' => '用户提交身份认证通知管理员', 'group' => '用户与互动', 'fields' => ['name' => '认证名称', 'time' => '认证时间', 'user' => '申请用户']],
'auth_apply_process' => ['label' => '身份认证处理后通知用户', 'group' => '用户与互动', 'fields' => ['status' => '审核结果', 'name' => '认证名称', 'desc' => '认证简介', 'time' => '处理时间']],
'report_user_admin' => ['label' => '收到举报后通知管理员', 'group' => '用户与互动', 'fields' => ['user' => '被举报用户', 'time' => '举报时间', 'reason' => '举报原因', 'desc' => '举报详情']],
'report_process' => ['label' => '处理举报后通知举报人', 'group' => '用户与互动', 'fields' => ['reason' => '举报原因', 'time' => '举报时间', 'desc' => '处理说明']],
'bind_phone' => ['label' => '绑定或修改手机号通知用户', 'group' => '用户与互动', 'fields' => ['name' => '用户昵称', 'time' => '操作时间', 'num' => '手机号']],
'bind_email' => ['label' => '绑定或修改邮箱通知用户', 'group' => '用户与互动', 'fields' => ['name' => '用户昵称', 'time' => '操作时间', 'num' => '邮箱']],
'comment_to_postauthor' => ['label' => '新评论通知文章作者', 'group' => '评论', 'fields' => ['name' => '评论用户', 'time' => '评论时间', 'content' => '评论内容', 'post' => '文章标题']],
'comment_to_parent' => ['label' => '评论有新回复通知用户', 'group' => '评论', 'fields' => ['name' => '评论用户', 'time' => '评论时间', 'content' => '评论内容']],
];
}
/** @return array{enabled:bool,templates:array<string,array{enabled:bool,id:string,keys:array<string,string>}>} */
public static function normalize(array $input): array {
$templates = [];
$provided = is_array($input['templates'] ?? null) ? $input['templates'] : [];
foreach (self::definitions() as $type => $definition) {
$item = is_array($provided[$type] ?? null) ? $provided[$type] : [];
$provided_keys = is_array($item['keys'] ?? null) ? $item['keys'] : [];
$keys = [];
foreach ($definition['fields'] as $field => $label) {
$raw = $provided_keys[$field] ?? '';
$value = is_string($raw) ? trim(sanitize_text_field($raw)) : '';
$keys[$field] = preg_match('/^[a-zA-Z][a-zA-Z0-9_]{0,63}$/', $value) ? $value : '';
}
$id = $item['id'] ?? '';
$templates[$type] = [
'enabled' => !empty($item['enabled']),
'id' => is_string($id) ? substr(trim(sanitize_text_field($id)), 0, 128) : '',
'keys' => $keys,
];
}
return ['enabled' => !empty($input['enabled']), 'templates' => $templates];
}
}
+1
View File
@@ -139,6 +139,7 @@ final class AuthApply {
if (class_exists('Jio\\Settings\\MailNotifications')) {
\Jio\Settings\MailNotifications::auth_reviewed($user_id, $status, $note, $reviewer_id);
}
do_action('jio_auth_application_reviewed', $user_id, $status, $note, $application);
return ['status' => $status];
}