Move all domain folders from src/AKSRT/{Domain} to src/{Domain}; remap
Composer PSR-4 "AKSRT\\": "src/"; update audit script + README + ADR
paths; remove dead tools/build-psr4-map.php (scanned deleted inc/classes).
269 lines
8.5 KiB
PHP
269 lines
8.5 KiB
PHP
<?php
|
|
/**
|
|
* @package AKSRT
|
|
*/
|
|
|
|
namespace AKSRT\Payment;
|
|
|
|
defined('ABSPATH') || exit;
|
|
|
|
class WeChatNotify {
|
|
|
|
private static $instance = null;
|
|
|
|
public static function get_instance(): self {
|
|
if (null === self::$instance) self::$instance = new self();
|
|
return self::$instance;
|
|
}
|
|
|
|
public function init(): void {
|
|
add_action('aksrt_notification_sent', [$this, 'maybe_send_wechat'], 10, 3);
|
|
add_action('wp_ajax_aksrt_wechat_status', [$this, 'ajax_status']);
|
|
}
|
|
|
|
/**
|
|
* 获取微信通知配置
|
|
*/
|
|
public function get_config(): array {
|
|
if (class_exists('\AKSRT\Settings\Settings')) {
|
|
return \AKSRT\Settings\Settings::get_wechat_settings();
|
|
}
|
|
return ['enabled' => false];
|
|
}
|
|
|
|
/**
|
|
* 发送微信模板消息
|
|
*
|
|
* @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();
|
|
|
|
if (empty($config['enabled'])) {
|
|
return false;
|
|
}
|
|
|
|
// 获取用户的微信 openid
|
|
$openid = get_user_meta($user_id, 'aksrt_wechat_openid', true);
|
|
if (empty($openid)) {
|
|
return false;
|
|
}
|
|
|
|
// 获取 access token
|
|
$access_token = $this->get_access_token($config);
|
|
if (empty($access_token)) {
|
|
if (defined('WP_DEBUG') && WP_DEBUG) {
|
|
error_log('[AKSRT WeChat] Failed to get access token');
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// 构建模板消息
|
|
$template_id = $config['template_id'] ?? '';
|
|
if (empty($template_id)) {
|
|
if (defined('WP_DEBUG') && WP_DEBUG) {
|
|
error_log('[AKSRT 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('[AKSRT 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('[AKSRT 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 = 'aksrt_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'] ?? [];
|
|
|
|
$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, 'aksrt_wechat_openid', sanitize_text_field($openid));
|
|
}
|
|
|
|
/**
|
|
* 解绑用户微信
|
|
*/
|
|
public function unbind_user(int $user_id): bool {
|
|
return (bool) delete_user_meta($user_id, 'aksrt_wechat_openid');
|
|
}
|
|
|
|
/**
|
|
* 监听站内通知,联动微信通知
|
|
*/
|
|
public function maybe_send_wechat(int $user_id, string $type, array $data): void {
|
|
$config = $this->get_config();
|
|
if (empty($config['enabled'])) return;
|
|
|
|
// 仅特定类型通知发送微信
|
|
$allowed_types = ['comment_reply', 'system_notice', 'order_paid', 'credits_change', 'forum_reply'];
|
|
if (!in_array($type, $allowed_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' => __('权限不足', 'aksrt')]);
|
|
}
|
|
|
|
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('aksrt_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 = 'aksrt_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' => ['comment_reply', 'system_notice', 'order_paid', 'credits_change', 'forum_reply'],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Get notification subscriptions for a user.
|
|
*/
|
|
public function get_user_subscriptions(int $user_id): array {
|
|
$subs = get_user_meta($user_id, 'aksrt_wechat_subscriptions', true);
|
|
if (!is_array($subs)) {
|
|
$subs = [
|
|
'comment_reply' => true,
|
|
'system_notice' => true,
|
|
'order_paid' => true,
|
|
'credits_change' => false,
|
|
'forum_reply' => false,
|
|
];
|
|
}
|
|
return $subs;
|
|
}
|
|
|
|
/**
|
|
* Update notification subscriptions for a user.
|
|
*/
|
|
public function update_user_subscriptions(int $user_id, array $subscriptions): bool {
|
|
$allowed = ['comment_reply', 'system_notice', 'order_paid', 'credits_change', 'forum_reply'];
|
|
$clean = [];
|
|
foreach ($allowed as $key) {
|
|
$clean[$key] = !empty($subscriptions[$key]);
|
|
}
|
|
return (bool) update_user_meta($user_id, 'aksrt_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, 'aksrt_wechat_openid', true);
|
|
if (empty($openid)) return false;
|
|
|
|
$subs = $this->get_user_subscriptions($user_id);
|
|
return !empty($subs[$type]);
|
|
}
|
|
}
|