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[a-z0-9_]+)/notify', array_merge($args, [ 'callback' => [$this, 'handle_notify'], ])); register_rest_route('aksrt/v1', '/payment/(?P[a-z0-9_]+)/return', array_merge($args, [ 'callback' => [$this, 'handle_return'], ])); register_rest_route('aksrt/v1', '/payment/(?P[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')]; } }