refactor(classes): organize inc/classes into domain folders

Move ~94 root class shells and root *-traits into domain directories
(core, auth, user, credits, content, payment/gateways, forum, shop,
sms, ai, messages, extensions, widgets, settings, admin). Update
bootstrap require paths, co-locate traits with owners, fix forum/user
relative requires, drop duplicate shop controller requires, and wire
home-modules load. Path-only; class names and init order unchanged.
This commit is contained in:
kate522 committed 2026-07-17 11:11:57 +08:00
1 parent 2a8fe68048
commit ce1c13b947
115 files changed
+171 -224

No files matched your search

@@ -0,0 +1,490 @@
<?php
/**
* AKSRT Payment Gateway — 支付网关适配器层
*
* 定义支付网关统一接口和基础实现。
* 具体网关(支付宝、微信支付等)通过子类实现。
*
* @package AKSRT
*/
defined('ABSPATH') || exit;
/**
* 支付网关统一接口
*/
interface AKSRT_Payment_Gateway_Interface {
/**
* 创建支付单
*
* @param array $order 订单数据
* @return array{success: bool, payment_url?: string, payment_data?: array, message?: string}
*/
public function create_payment(array $order): array;
/**
* 验证回调
*
* @param WP_REST_Request $request 回调请求
* @return array{success: bool, order_number?: string, amount?: float, transaction_id?: string, message?: string}
*/
public function verify_callback(WP_REST_Request $request): array;
/**
* 查询支付状态
*
* @param string $order_number 订单号
* @return array{success: bool, status?: string, transaction_id?: string, message?: string}
*/
public function query_status(string $order_number): array;
/**
* 退款
*
* @param string $order_number 订单号
* @param float $amount 退款金额
* @return array{success: bool, refund_id?: string, message?: string}
*/
public function refund(string $order_number, float $amount): array;
/**
* 获取网关显示名称
*/
public function get_name(): string;
/**
* 获取网关标识
*/
public function get_id(): string;
/**
* 网关是否已配置
*/
public function is_configured(): bool;
}
/**
* 支付网关管理器
*/
class AKSRT_Payment_Gateway {
private static $instance = null;
private $gateways = [];
public static function get_instance(): self {
if (null === self::$instance) self::$instance = new self();
return self::$instance;
}
/**
* 注册网关
*/
public function register(string $id, AKSRT_Payment_Gateway_Interface $gateway): void {
$this->gateways[$id] = $gateway;
}
/**
* 获取网关
*/
public function get_gateway(string $id): ?AKSRT_Payment_Gateway_Interface {
return $this->gateways[$id] ?? null;
}
/**
* 获取所有已注册网关
*
* @return AKSRT_Payment_Gateway_Interface[]
*/
public function get_all(): array {
return $this->gateways;
}
/**
* 获取可用网关列表(已配置的)
*/
public function get_available(): array {
$available = [];
foreach ($this->gateways as $id => $gateway) {
if ($gateway->is_configured()) {
$available[$id] = $gateway;
}
}
return $available;
}
/**
* 获取网关状态摘要(用于后台展示)
*/
public function get_status_summary(): array {
$summary = [];
foreach ($this->gateways as $id => $gateway) {
$summary[] = [
'id' => $id,
'name' => $gateway->get_name(),
'configured' => $gateway->is_configured(),
'available' => $gateway->is_configured(),
];
}
return $summary;
}
/**
* 绑定支付回调路由。必须在 functions.php 中初始化。
*
* 此前网关 create_payment() 把 notify_url/return_url 指向
* /wp-json/aksrt/v1/payment/{gateway}/{notify|return|cancel},但这些路由
* 从未注册,导致所有跳转型网关(支付宝/微信/PayPal/Stripe)无法完成支付。
*/
public function init(): void {
add_action('rest_api_init', [$this, 'register_routes']);
}
public function register_routes(): void {
$args = [
'methods' => ['GET', 'POST'],
'permission_callback' => '__return_true', // 真实性由各网关签名校验保证
'args' => [
'gateway' => ['sanitize_callback' => 'sanitize_key'],
],
];
register_rest_route('aksrt/v1', '/payment/(?P<gateway>[a-z0-9_]+)/notify', array_merge($args, [
'callback' => [$this, 'handle_notify'],
]));
register_rest_route('aksrt/v1', '/payment/(?P<gateway>[a-z0-9_]+)/return', array_merge($args, [
'callback' => [$this, 'handle_return'],
]));
register_rest_route('aksrt/v1', '/payment/(?P<gateway>[a-z0-9_]+)/cancel', array_merge($args, [
'callback' => [$this, 'handle_cancel'],
]));
}
/**
* 异步通知(服务器到服务器)。验签 → 到账 → 输出网关期望的 ACK。
*/
public function handle_notify(WP_REST_Request $request) {
$gateway_id = sanitize_key((string) $request->get_param('gateway'));
$gateway = $this->get_gateway($gateway_id);
if (!$gateway) {
return new WP_REST_Response('gateway not found', 404);
}
$verify = $gateway->verify_callback($request);
$verified = !empty($verify['success']);
$paid = $verified && ($verify['status'] ?? '') === 'paid';
if ($paid) {
$this->complete_order($gateway_id, $verify);
}
// 网关各自要求的 ACK 格式
if ($gateway_id === 'wechatpay' && method_exists($gateway, 'output_callback_response')) {
$gateway->output_callback_response(['success' => $verified]); // exit
}
if ($gateway_id === 'alipay') {
// 支付宝异步通知要求返回纯文本 success,否则会重复通知
header('Content-Type: text/plain; charset=utf-8');
echo $verified ? 'success' : 'fail';
exit;
}
// Stripe / PayPal webhook:200 表示已接收;验签失败返回 400 触发重试
return new WP_REST_Response(['received' => $verified], $verified ? 200 : 400);
}
/**
* 用户浏览器同步返回。尽力确认到账(兜底异步通知延迟),再跳回订单中心。
*/
public function handle_return(WP_REST_Request $request) {
$gateway_id = sanitize_key((string) $request->get_param('gateway'));
$order_number = sanitize_text_field((string) ($request->get_param('order')
?: $request->get_param('out_trade_no')
?: ''));
$gateway = $this->get_gateway($gateway_id);
$status = 'pending';
if ($gateway) {
if ($gateway_id === 'alipay') {
// 支付宝同步返回带签名参数,可直接验签作为到账兜底
$verify = $gateway->verify_callback($request);
if (!empty($verify['success'])) {
$order_number = $order_number ?: sanitize_text_field((string) ($verify['order_number'] ?? ''));
if (($verify['status'] ?? '') === 'paid' && $this->complete_order($gateway_id, $verify)) {
$status = 'success';
}
}
} elseif ($gateway_id === 'paypal' && method_exists($gateway, 'capture_order')) {
// PayPal v2:用户批准后在 return 时捕获(token = PayPal 订单号)
$paypal_order_id = sanitize_text_field((string) ($request->get_param('token') ?: ''));
if ($paypal_order_id !== '') {
$cap = $gateway->capture_order($paypal_order_id);
if (!empty($cap['success']) && ($cap['status'] ?? '') === 'paid') {
$order_number = $order_number ?: sanitize_text_field((string) ($cap['order_number'] ?? ''));
$cap['order_number'] = $order_number;
if ($this->complete_order($gateway_id, $cap)) {
$status = 'success';
}
}
}
} elseif ($order_number !== '') {
// 其余网关:主动查询订单状态兜底
$q = $gateway->query_status($order_number);
if (!empty($q['success']) && ($q['status'] ?? '') === 'paid') {
$done = $this->complete_order($gateway_id, [
'order_number' => $order_number,
'amount' => 0, // 由网关 API 已确认到账,金额校验交给 notify 路径
'transaction_id' => $q['transaction_id'] ?? '',
'status' => 'paid',
]);
if ($done) {
$status = 'success';
}
}
}
}
$this->redirect_to_orders($order_number, $status);
return null;
}
/**
* 用户取消支付,跳回订单中心。
*/
public function handle_cancel(WP_REST_Request $request) {
$order_number = sanitize_text_field((string) ($request->get_param('order')
?: $request->get_param('out_trade_no')
?: ''));
$this->redirect_to_orders($order_number, 'cancelled');
return null;
}
/**
* 幂等、带金额校验的订单完成。仅由验签通过的回调调用。
*
* @param string $gateway_id 网关 ID
* @param array $verify 网关 verify_callback / query 结果
* @return bool
*/
public function complete_order(string $gateway_id, array $verify): bool {
$order_number = sanitize_text_field((string) ($verify['order_number'] ?? ''));
if ($order_number === '' || !class_exists('AKSRT_Order_Repository')) {
return false;
}
$repo = AKSRT_Order_Repository::get_instance();
$order = $repo->get($order_number);
if (!$order) {
return false;
}
$current = (string) ($order['status'] ?? '');
// 幂等:已支付/已交付/已完成视为成功,不重复处理(回调可能多次到达)
if (in_array($current, ['paid', 'delivered', 'completed'], true)) {
return true;
}
// 只允许从未支付态完成
if (!in_array($current, ['created', 'pending'], true)) {
return false;
}
// 金额校验,防止前端/回调金额被篡改
$expected = (float) ($order['amount'] ?? 0);
$paid_amount = (float) ($verify['amount'] ?? 0);
if ($expected > 0 && $paid_amount > 0 && abs($expected - $paid_amount) > 0.01) {
if (defined('WP_DEBUG') && WP_DEBUG) {
error_log(sprintf('[AKSRT Payment] amount mismatch order=%s expected=%.2f paid=%.2f gateway=%s', $order_number, $expected, $paid_amount, $gateway_id));
}
return false;
}
return $repo->update_status($order_number, AKSRT_Order::STATUS_PAID, [
'payment_method' => $gateway_id,
'payment_transaction_id' => sanitize_text_field((string) ($verify['transaction_id'] ?? '')),
'note' => __('支付网关回调确认到账。', 'aksrt'),
]);
}
private function redirect_to_orders(string $order_number, string $status): void {
$url = add_query_arg(array_filter([
'payment' => sanitize_key($status),
'order' => $order_number,
]), $this->orders_page_url());
nocache_headers();
wp_safe_redirect($url);
exit;
}
private function orders_page_url(): string {
$pages = get_posts([
'post_type' => 'page',
'post_status' => 'publish',
'posts_per_page' => 1,
'meta_key' => '_wp_page_template',
'meta_value' => 'templates/pages/page-orders.php',
'fields' => 'ids',
'no_found_rows' => true,
]);
if (!empty($pages)) {
return (string) get_permalink((int) $pages[0]);
}
return home_url('/orders/');
}
}
/**
* 站内余额支付网关(内置实现)
*
* 优先扣现金钱包(AKSRT_Balance / pay_balance);未启用时回退到积分。
*/
class AKSRT_Balance_Gateway implements AKSRT_Payment_Gateway_Interface {
public function get_id(): string {
return 'balance';
}
public function get_name(): string {
return __('站内余额', 'aksrt');
}
public function is_configured(): bool {
$wallet_on = class_exists('AKSRT_Balance') && AKSRT_Balance::enabled();
$credits_on = class_exists('AKSRT_Credits');
if (!$wallet_on && !$credits_on) {
return false;
}
if (class_exists('AKSRT_Settings_Payment_Runtime')) {
return AKSRT_Settings_Payment_Runtime::is_gateway_enabled('balance');
}
return true;
}
public function create_payment(array $order): array {
$user_id = (int) ($order['user_id'] ?? 0);
$amount = round((float) ($order['amount'] ?? 0), 2);
$order_number = (string) ($order['order_number'] ?? '');
// Prefer cash wallet when pay_balance_s is on.
if (class_exists('AKSRT_Balance') && AKSRT_Balance::enabled()) {
if ($user_id <= 0) {
return ['success' => false, 'message' => __('请先登录', 'aksrt')];
}
$balance = AKSRT_Balance::balance($user_id);
if ($balance < $amount) {
return ['success' => false, 'message' => __('余额不足', 'aksrt')];
}
$result = AKSRT_Balance::deduct(
$user_id,
$amount,
'order_payment',
['type' => 'order', 'id' => 0, 'order' => $order_number],
sprintf(__('订单 %s 支付', 'aksrt'), $order_number)
);
if (is_wp_error($result)) {
return ['success' => false, 'message' => $result->get_error_message() ?: __('扣款失败', 'aksrt')];
}
return [
'success' => true,
'transaction_id' => 'BAL_' . ($order_number !== '' ? $order_number : (string) time()),
'message' => __('余额支付成功', 'aksrt'),
];
}
// Fallback: legacy points-as-balance (1 元 = 100 积分).
if (!class_exists('AKSRT_Credits')) {
return ['success' => false, 'message' => __('积分系统未启用', 'aksrt')];
}
$credits_cost = (int) ceil($amount * 100);
$balance = AKSRT_Credits::balance($user_id);
if ($balance < $credits_cost) {
return ['success' => false, 'message' => __('余额不足', 'aksrt')];
}
$result = AKSRT_Credits::deduct(
$user_id,
$credits_cost,
'order_payment',
['order' => $order_number],
sprintf(__('订单 %s 支付', 'aksrt'), $order_number)
);
if (is_wp_error($result)) {
return ['success' => false, 'message' => $result->get_error_message() ?: __('扣款失败', 'aksrt')];
}
return [
'success' => true,
'transaction_id' => 'BAL_' . ($order_number !== '' ? $order_number : (string) time()),
'message' => __('余额支付成功', 'aksrt'),
];
}
public function verify_callback(WP_REST_Request $request): array {
// Balance payment is synchronous — no callback needed
return ['success' => false, 'message' => __('余额支付无需回调验证', 'aksrt')];
}
public function query_status(string $order_number): array {
$order_repo = AKSRT_Order_Repository::get_instance();
$order = $order_repo->get($order_number);
if (!$order) {
return ['success' => false, 'message' => __('订单不存在', 'aksrt')];
}
return [
'success' => true,
'status' => $order['status'],
'transaction_id' => $order['payment_transaction_id'] ?? '',
];
}
public function refund(string $order_number, float $amount): array {
$order_repo = AKSRT_Order_Repository::get_instance();
$order = $order_repo->get($order_number);
if (!$order) {
return ['success' => false, 'message' => __('订单不存在', 'aksrt')];
}
$user_id = (int) $order['user_id'];
$amount = round((float) $amount, 2);
if (class_exists('AKSRT_Balance') && AKSRT_Balance::enabled()) {
$result = AKSRT_Balance::add(
$user_id,
$amount,
'order_refund',
['type' => 'order', 'order' => $order_number],
sprintf(__('订单 %s 退款', 'aksrt'), $order_number)
);
if (is_wp_error($result)) {
return ['success' => false, 'message' => $result->get_error_message() ?: __('退款失败', 'aksrt')];
}
return ['success' => true, 'refund_id' => 'BAL_REF_' . $order_number, 'message' => __('退款成功', 'aksrt')];
}
if (!class_exists('AKSRT_Credits')) {
return ['success' => false, 'message' => __('积分系统未启用', 'aksrt')];
}
$credits_refund = (int) ceil($amount * 100);
$result = AKSRT_Credits::add(
$user_id,
$credits_refund,
'order_refund',
['order' => $order_number],
sprintf(__('订单 %s 退款', 'aksrt'), $order_number)
);
if (is_wp_error($result)) {
return ['success' => false, 'message' => $result->get_error_message() ?: __('退款失败', 'aksrt')];
}
return ['success' => true, 'refund_id' => 'BAL_REF_' . $order_number, 'message' => __('退款成功', 'aksrt')];
}
}