仰望星辰工作室

fix: 统一修复支付回调路由兼容与前端支付跳转逻辑

Y yang1145 提交于 2026-08-15 00:42 · 754236a ·父提交 183024d
fix: 统一修复支付回调路由兼容与前端支付跳转逻辑

1.  调整各支付插件返回的接口提示语为标准'ok',适配SPA客户端校验
2.  重构支付回调地址,增加index.php?_r=前缀兼容无伪静态环境
3.  优化epay回调路由支持GET/POST请求,兼容不同通知场景
4.  修复前端支付跳转逻辑,避免弹窗拦截问题并更新注释说明
8 个文件变更 +38 -19 13172193298@163.com
•app_plugins/alipay_official/admin/settings.php +3 -2
•app_plugins/alipay_official/bootstrap.php +3 -2
•app_plugins/balance/bootstrap.php +2 -1
•app_plugins/docker_shop/bootstrap.php +1 -1
•app_plugins/epay/admin/settings.php +3 -2
•app_plugins/epay/bootstrap.php +19 -3
•app_plugins/hosting_shop/bootstrap.php +1 -1
•templates/tdesign/spa/src/account/api/plugins.js +6 -7
变更内容
diff --git a/app_plugins/alipay_official/admin/settings.php b/app_plugins/alipay_official/admin/settings.php
index edf2b4c..5823fdc 100644
--- a/app_plugins/alipay_official/admin/settings.php
+++ b/app_plugins/alipay_official/admin/settings.php
@@ -26,8 +26,9 @@ $siteRoot = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' ? 'https://
 if (substr($siteRoot, -6) === '/admin') {
 	$siteRoot = substr($siteRoot, 0, -6);
 }
-$notifyUrl = $siteRoot . '/pay/alipay_official/notify';
-$returnUrl = $siteRoot . '/pay/alipay_official/return';
+// 回调地址统一用 index.php?_r= 兼容路由:不依赖服务器伪静态重写
+$notifyUrl = $siteRoot . '/index.php?_r=/pay/alipay_official/notify';
+$returnUrl = $siteRoot . '/index.php?_r=/pay/alipay_official/return';
 ?>
 
 <div class="mn-set-card">
diff --git a/app_plugins/alipay_official/bootstrap.php b/app_plugins/alipay_official/bootstrap.php
index ce5634f..a624a79 100644
--- a/app_plugins/alipay_official/bootstrap.php
+++ b/app_plugins/alipay_official/bootstrap.php
@@ -73,8 +73,9 @@ mnbt_register_payment('alipay_official', [
 		}
 		$siteurl = isset($order['siteurl']) ? $order['siteurl'] : '';
 		$siteurl = rtrim((string)$siteurl, '/') . '/';
-		$notifyUrl = $siteurl . 'pay/alipay_official/notify';
-		$returnUrl = $siteurl . 'pay/alipay_official/return';
+		// 回调地址统一用 index.php?_r= 兼容路由:不依赖服务器伪静态重写(同 epay)
+		$notifyUrl = $siteurl . 'index.php?_r=/pay/alipay_official/notify';
+		$returnUrl = $siteurl . 'index.php?_r=/pay/alipay_official/return';
 
 		$params = [
 			'out_trade_no' => $order['out_trade_no'],
diff --git a/app_plugins/balance/bootstrap.php b/app_plugins/balance/bootstrap.php
index 8fb02b1..2ea5fdd 100644
--- a/app_plugins/balance/bootstrap.php
+++ b/app_plugins/balance/bootstrap.php
@@ -198,7 +198,8 @@ mnbt_register_route('POST', '/balance/api/create_recharge', function ($params, $
 	}
 
 	// 返回支付 HTML,前端用 document.write 输出跳转
-	balance_json('正在跳转到支付页面', ['html' => $html]);
+	// code 必须为 'ok':支付发起成功(HTML 已生成),否则 SPA 等客户端会按失败处理而不跳转
+	balance_json('ok', ['html' => $html]);
 });
 
 /* ============================================================
diff --git a/app_plugins/docker_shop/bootstrap.php b/app_plugins/docker_shop/bootstrap.php
index c2909aa..dcf4692 100644
--- a/app_plugins/docker_shop/bootstrap.php
+++ b/app_plugins/docker_shop/bootstrap.php
@@ -287,7 +287,7 @@ mnbt_register_route('POST', '/docker-shop/api/create_order', function ($params,
 		docker_shop_json('支付方式不可用,请检查支付插件是否已启用');
 	}
 
-	docker_shop_json('正在跳转到支付页面', ['html' => $html, 'order_no' => $order_no]);
+	docker_shop_json('ok', ['html' => $html, 'order_no' => $order_no]);
 });
 
 // 重置 Docker 账号密码
diff --git a/app_plugins/epay/admin/settings.php b/app_plugins/epay/admin/settings.php
index c1fcbd0..684a00b 100644
--- a/app_plugins/epay/admin/settings.php
+++ b/app_plugins/epay/admin/settings.php
@@ -26,8 +26,9 @@ $siteRoot = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' ? 'https://
 if (substr($siteRoot, -6) === '/admin') {
 	$siteRoot = substr($siteRoot, 0, -6);
 }
-$notifyUrl = $siteRoot . '/pay/epay/notify';
-$returnUrl = $siteRoot . '/pay/epay/return';
+// 回调地址统一用 index.php?_r= 兼容路由:不依赖服务器伪静态重写
+$notifyUrl = $siteRoot . '/index.php?_r=/pay/epay/notify';
+$returnUrl = $siteRoot . '/index.php?_r=/pay/epay/return';
 ?>
 
 <div class="mn-set-card">
diff --git a/app_plugins/epay/bootstrap.php b/app_plugins/epay/bootstrap.php
index dfa2120..fde37c7 100644
--- a/app_plugins/epay/bootstrap.php
+++ b/app_plugins/epay/bootstrap.php
@@ -92,12 +92,17 @@ mnbt_register_payment('epay', [
 		$siteurl = isset($order['siteurl']) ? $order['siteurl'] : '';
 		// 站点根 URL 末尾保证带斜杠
 		$siteurl = rtrim((string)$siteurl, '/') . '/';
+		// 回调地址统一用 index.php?_r= 兼容路由:不依赖服务器伪静态重写。
+		// 部分环境(未配置 rewrite 规则)下 /pay/epay/notify 会直接返回 nginx 404,
+		// 导致易支付网关异步通知收不到、订单无法结算(支付成功但余额不入账)。
+		$notifyUrl = $siteurl . 'index.php?_r=/pay/epay/notify';
+		$returnUrl = $siteurl . 'index.php?_r=/pay/epay/return';
 
 		$params = [
 			'type'          => $method,           // alipay / wxpay / qqpay
 			'out_trade_no'  => $order['out_trade_no'],
-			'notify_url'    => $siteurl . 'pay/epay/notify',
-			'return_url'    => $siteurl . 'pay/epay/return',
+			'notify_url'    => $notifyUrl,
+			'return_url'    => $returnUrl,
 			'name'          => $order['name'],
 			'money'         => $order['money'],
 		];
@@ -107,15 +112,24 @@ mnbt_register_payment('epay', [
 
 // ============================================================
 //  注册回调路由:/pay/epay/notify(异步通知)
+//  注意:路由方式必须为 *(GET+POST 都接收)——
+//  部分易支付变体(如 pay1987)的自动通知/后台补发使用 GET 携带参数,
+//  只注册 POST 会导致通知命中不了路由、订单无法结算。
 // ============================================================
-mnbt_register_route('POST', '/pay/epay/notify', function ($params, $ctx) {
+mnbt_register_route('*', '/pay/epay/notify', function ($params, $ctx) {
 	@header('Content-Type: text/plain; charset=UTF-8');
 	if (!epay_is_configured()) {
 		echo 'fail';
 		return;
 	}
 	$c = epay_get_config();
+	// 兼容 POST / GET 两种通知方式
 	$data = $_POST;
+	if (empty($data)) {
+		$data = $_GET;
+		// 路由参数 _r 是我们拼接的,不参与网关签名,验签前必须剔除
+		unset($data['_r']);
+	}
 	if (empty($data) || empty($data['sign'])) {
 		mnbt_pay_log('易支付异步通知无数据或缺少 sign', '回调异常', $data['out_trade_no'] ?? '');
 		echo 'fail';
@@ -152,6 +166,8 @@ mnbt_register_route('GET', '/pay/epay/return', function ($params, $ctx) {
 	}
 	$c = epay_get_config();
 	$data = $_GET;
+	// 剔除路由参数 _r(不参与签名)
+	unset($data['_r']);
 	$ok = false;
 	if (!empty($data) && !empty($data['sign'])) {
 		$ok = Epay_Core::verifySign($data, $c['key'], (string)$data['sign']);
diff --git a/app_plugins/hosting_shop/bootstrap.php b/app_plugins/hosting_shop/bootstrap.php
index 570c8b1..6e5eea0 100644
--- a/app_plugins/hosting_shop/bootstrap.php
+++ b/app_plugins/hosting_shop/bootstrap.php
@@ -323,7 +323,7 @@ mnbt_register_route('POST', '/shop/api/create_order', function ($params, $ctx) {
 		hosting_json('支付方式不可用,请检查支付插件是否已启用');
 	}
 
-	hosting_json('正在跳转到支付页面', ['html' => $html, 'order_no' => $order_no]);
+	hosting_json('ok', ['html' => $html, 'order_no' => $order_no]);
 });
 
 /* ============================================================
diff --git a/templates/tdesign/spa/src/account/api/plugins.js b/templates/tdesign/spa/src/account/api/plugins.js
index 7b9a215..7f271a4 100644
--- a/templates/tdesign/spa/src/account/api/plugins.js
+++ b/templates/tdesign/spa/src/account/api/plugins.js
@@ -26,17 +26,16 @@ export function centsToYuan(cents) {
 
 /**
  * 处理支付跳转响应(create_recharge / create_order 通用)
- * @returns {boolean} 是否已触发跳转(html 弹窗 / redirect 跳转)
+ * @returns {boolean} 是否已触发跳转(html 写入当前文档 / redirect 跳转)
  */
 export function goPay(res) {
   const data = res?.data || {}
   if (data.html) {
-    const win = window.open('', '_blank')
-    if (win) {
-      win.document.open()
-      win.document.write(data.html)
-      win.document.close()
-    }
+    // 直接写入当前文档触发表单自动提交跳转支付页。
+    // 不能用 window.open('', '_blank'):经过 await 后已无用户手势,会被浏览器弹窗拦截导致不跳转。
+    document.open()
+    document.write(data.html)
+    document.close()
     return true
   }
   if (data.redirect) {