diff --git a/app/Http/Controllers/VipPaymentController.php b/app/Http/Controllers/VipPaymentController.php
index 989b6b6..91d0abb 100644
--- a/app/Http/Controllers/VipPaymentController.php
+++ b/app/Http/Controllers/VipPaymentController.php
@@ -16,6 +16,10 @@ use Illuminate\Http\Request;
use Illuminate\Http\Response;
use RuntimeException;
+/**
+ * 前台 VIP 支付控制器
+ * 负责接收用户选择的支付渠道,下发支付中心订单并处理回调结果。
+ */
class VipPaymentController extends Controller
{
/**
@@ -41,10 +45,11 @@ class VipPaymentController extends Controller
}
$vipLevel = VipLevel::query()->findOrFail((int) $request->validated('vip_level_id'));
+ $provider = (string) $request->validated('provider');
try {
// 先创建本地订单,再向支付中心发起下单,确保回调时有本地单据可追踪。
- $vipPaymentOrder = $this->vipPaymentService->createLocalOrder($request->user(), $vipLevel);
+ $vipPaymentOrder = $this->vipPaymentService->createLocalOrder($request->user(), $vipLevel, $provider);
$remoteOrder = $this->vipPaymentService->createRemoteOrder($vipPaymentOrder);
$payUrl = (string) ($remoteOrder['pay_url'] ?? '');
diff --git a/app/Http/Requests/CreateVipPaymentOrderRequest.php b/app/Http/Requests/CreateVipPaymentOrderRequest.php
index a57bf20..f7affaf 100644
--- a/app/Http/Requests/CreateVipPaymentOrderRequest.php
+++ b/app/Http/Requests/CreateVipPaymentOrderRequest.php
@@ -8,7 +8,12 @@
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
+use Illuminate\Validation\Validator;
+/**
+ * 创建 VIP 支付订单请求
+ * 负责校验会员购买等级与支付渠道,确保下单时明确指定支付方式。
+ */
class CreateVipPaymentOrderRequest extends FormRequest
{
/**
@@ -28,16 +33,14 @@ class CreateVipPaymentOrderRequest extends FormRequest
{
return [
'vip_level_id' => ['required', 'integer', 'exists:vip_levels,id'],
+ 'provider' => ['required', 'string', 'in:alipay,wechat'],
];
}
/**
* 配置验证器实例。
- *
- * @param \Illuminate\Validation\Validator $validator
- * @return void
*/
- public function withValidator($validator): void
+ public function withValidator(Validator $validator): void
{
$validator->after(function ($validator) {
$user = $this->user();
@@ -71,6 +74,8 @@ class CreateVipPaymentOrderRequest extends FormRequest
return [
'vip_level_id.required' => '请选择要购买的 VIP 等级',
'vip_level_id.exists' => '所选 VIP 等级不存在或已被删除',
+ 'provider.required' => '请选择支付方式',
+ 'provider.in' => '当前支付方式不受支持,请重新选择',
];
}
}
diff --git a/app/Services/VipPaymentCenterClient.php b/app/Services/VipPaymentCenterClient.php
index 2b811d8..a2bf244 100644
--- a/app/Services/VipPaymentCenterClient.php
+++ b/app/Services/VipPaymentCenterClient.php
@@ -13,6 +13,10 @@ use Illuminate\Http\Client\Response;
use Illuminate\Support\Facades\Http;
use RuntimeException;
+/**
+ * NovaLink 支付中心客户端
+ * 负责构建开放接口签名请求,并与远端支付中心完成下单和查单通信。
+ */
class VipPaymentCenterClient
{
/**
@@ -65,7 +69,8 @@ class VipPaymentCenterClient
'subject' => $vipPaymentOrder->subject,
'body' => '聊天室 VIP 会员购买:'.$vipPaymentOrder->vip_name,
'amount' => number_format((float) $vipPaymentOrder->amount, 2, '.', ''),
- 'channel' => 'web',
+ // 支付中心要求商户显式指定 provider,不能依赖平台自动推断。
+ 'provider' => $vipPaymentOrder->provider,
'business_type' => 'chatroom_vip',
'sync_return_url' => $syncReturnUrl,
'async_notify_url' => $asyncNotifyUrl,
diff --git a/app/Services/VipPaymentService.php b/app/Services/VipPaymentService.php
index 6138505..0b79d38 100644
--- a/app/Services/VipPaymentService.php
+++ b/app/Services/VipPaymentService.php
@@ -40,11 +40,13 @@ class VipPaymentService
*
* @param User $user 购买用户
* @param VipLevel $vipLevel 目标 VIP 等级
+ * @param string $provider 用户选择的支付渠道
*/
- public function createLocalOrder(User $user, VipLevel $vipLevel): VipPaymentOrder
+ public function createLocalOrder(User $user, VipLevel $vipLevel, string $provider): VipPaymentOrder
{
$currentVip = $user->isVip() ? $user->vipLevel : null;
$isUpgrade = $currentVip && $vipLevel->isHigherThan($currentVip);
+ $provider = $this->normalizeProvider($provider);
// 如果已经是该等级或更高级别,且不是永久会员续费(逻辑上续费应该用原价,但此处 user 需求是升级补差价)
// 这里我们主要处理补差价升级逻辑。
@@ -73,7 +75,8 @@ class VipPaymentService
'status' => 'created',
'amount' => $price,
'subject' => ($isUpgrade ? '【升级】' : '购买').' VIP 会员 - '.$vipLevel->name,
- 'provider' => 'alipay',
+ // 下单时必须固化用户选择的支付渠道,避免支付中心拒绝未指定 provider 的请求。
+ 'provider' => $provider,
'vip_name' => $vipLevel->name,
'vip_duration_days' => (int) $vipLevel->duration_days,
'meta' => [
@@ -250,6 +253,22 @@ class VipPaymentService
return 'VPO'.date('YmdHis').random_int(1000, 9999);
}
+ /**
+ * 规范化并校验支付渠道
+ *
+ * @param string $provider 前端提交的支付渠道
+ */
+ private function normalizeProvider(string $provider): string
+ {
+ $provider = trim(strtolower($provider));
+
+ if (! in_array($provider, ['alipay', 'wechat'], true)) {
+ throw new RuntimeException('当前支付方式不受支持。');
+ }
+
+ return $provider;
+ }
+
/**
* 向用户当前在线的聊天室广播 VIP 购买成功喜报与烟花特效。
*
diff --git a/resources/views/chat/partials/layout/toolbar.blade.php b/resources/views/chat/partials/layout/toolbar.blade.php
index 1e0d381..b31c249 100644
--- a/resources/views/chat/partials/layout/toolbar.blade.php
+++ b/resources/views/chat/partials/layout/toolbar.blade.php
@@ -1695,11 +1695,24 @@ async function generateWechatBindCode() {
/ ${v.duration_days}天
${showUpgradeInfo ? `
已省 ¥${(v.price - v.upgrade_price).toFixed(2)}
` : ''}
-
+ ${!d.vipPaymentEnabled || isDisabled
+ ? ``
+ : `
+
+
+
+ ${btnText}后将跳转到对应支付页面
`
+ }
`;
@@ -1798,7 +1811,7 @@ async function generateWechatBindCode() {
}
}
- window.buyVip = function(levelId) {
+ window.buyVip = function(levelId, provider = 'alipay') {
// 这里我们模拟提交表单,因为支付逻辑通常需要页面跳转
// 修改为在新窗口打开支付,避免聊天室页面丢失
const form = document.createElement('form');
@@ -1818,6 +1831,12 @@ async function generateWechatBindCode() {
idInput.value = levelId;
form.appendChild(idInput);
+ const providerInput = document.createElement('input');
+ providerInput.type = 'hidden';
+ providerInput.name = 'provider';
+ providerInput.value = provider;
+ form.appendChild(providerInput);
+
document.body.appendChild(form);
form.submit();
document.body.removeChild(form);
@@ -1825,7 +1844,8 @@ async function generateWechatBindCode() {
// 提交后关闭弹窗并提示用户
closeVipModal();
if (window.chatDialog) {
- window.chatDialog.alert('正在为您前往支付中心,请在新页面完成支付。', '支付提示', '#3b82f6');
+ const providerText = provider === 'wechat' ? '微信支付二维码页' : '支付宝支付页';
+ window.chatDialog.alert(`正在为您打开${providerText},请在新页面完成支付。`, '支付提示', '#3b82f6');
}
};
diff --git a/resources/views/rooms/guide.blade.php b/resources/views/rooms/guide.blade.php
index d365a4b..cb5aba9 100644
--- a/resources/views/rooms/guide.blade.php
+++ b/resources/views/rooms/guide.blade.php
@@ -406,12 +406,18 @@
@if ($vip->price > 0 && $vipPaymentEnabled)
@elseif ($vip->price > 0)
diff --git a/resources/views/vip/center.blade.php b/resources/views/vip/center.blade.php
index 441eb30..d35c73f 100644
--- a/resources/views/vip/center.blade.php
+++ b/resources/views/vip/center.blade.php
@@ -165,14 +165,29 @@
@if ($vipPaymentEnabled)
-
@endif
diff --git a/tests/Feature/Feature/VipPaymentIntegrationTest.php b/tests/Feature/Feature/VipPaymentIntegrationTest.php
index a5d2b0d..eaccd4c 100644
--- a/tests/Feature/Feature/VipPaymentIntegrationTest.php
+++ b/tests/Feature/Feature/VipPaymentIntegrationTest.php
@@ -60,14 +60,68 @@ class VipPaymentIntegrationTest extends TestCase
$response = $this->actingAs($user)->post(route('vip.payment.store'), [
'vip_level_id' => $vipLevel->id,
+ 'provider' => 'alipay',
]);
$response->assertRedirect('https://novalink.test/payment/checkout/alipay/PO202604111200001234');
+ Http::assertSent(function ($request) {
+ $data = $request->data();
+
+ return $request->url() === 'https://novalink.test/api/open/v1/pay/orders'
+ && ($data['provider'] ?? null) === 'alipay';
+ });
$this->assertDatabaseHas('vip_payment_orders', [
'user_id' => $user->id,
'vip_level_id' => $vipLevel->id,
'payment_order_no' => 'PO202604111200001234',
'status' => 'pending',
+ 'provider' => 'alipay',
+ ]);
+ }
+
+ /**
+ * 测试用户可以选择微信支付并跳转到平台二维码页面
+ */
+ public function test_user_can_create_wechat_vip_payment_order_and_redirect_to_wechat_checkout_page(): void
+ {
+ $this->seedVipPaymentConfig();
+
+ $user = User::factory()->create();
+ $vipLevel = VipLevel::factory()->create([
+ 'name' => '黄金会员',
+ 'price' => 68,
+ 'duration_days' => 30,
+ ]);
+
+ Http::fake([
+ 'https://novalink.test/api/open/v1/pay/orders' => Http::response([
+ 'success' => true,
+ 'message' => '支付单创建成功',
+ 'data' => [
+ 'payment_order_no' => 'PO202604131530001234',
+ 'pay_url' => 'https://novalink.test/payment/checkout/wechat/PO202604131530001234',
+ ],
+ ], 201),
+ ]);
+
+ $response = $this->actingAs($user)->post(route('vip.payment.store'), [
+ 'vip_level_id' => $vipLevel->id,
+ 'provider' => 'wechat',
+ ]);
+
+ $response->assertRedirect('https://novalink.test/payment/checkout/wechat/PO202604131530001234');
+ Http::assertSent(function ($request) {
+ $data = $request->data();
+
+ return $request->url() === 'https://novalink.test/api/open/v1/pay/orders'
+ && ($data['provider'] ?? null) === 'wechat';
+ });
+ $this->assertDatabaseHas('vip_payment_orders', [
+ 'user_id' => $user->id,
+ 'vip_level_id' => $vipLevel->id,
+ 'payment_order_no' => 'PO202604131530001234',
+ 'status' => 'pending',
+ 'provider' => 'wechat',
]);
}
@@ -89,6 +143,7 @@ class VipPaymentIntegrationTest extends TestCase
'user_id' => $user->id,
'vip_level_id' => $vipLevel->id,
'amount' => 88.00,
+ 'provider' => 'wechat',
'vip_name' => $vipLevel->name,
'vip_duration_days' => $vipLevel->duration_days,
'payment_order_no' => 'PO202604111530001234',
@@ -100,8 +155,8 @@ class VipPaymentIntegrationTest extends TestCase
'merchant_order_no' => $order->merchant_order_no,
'status' => 'paid',
'amount' => '88.00',
- 'provider' => 'alipay',
- 'provider_trade_no' => '2026041122001499999999999999',
+ 'provider' => 'wechat',
+ 'provider_trade_no' => '4200002512202604139876543210',
'paid_at' => '2026-04-11 15:35:12',
];
@@ -146,6 +201,7 @@ class VipPaymentIntegrationTest extends TestCase
$this->assertDatabaseHas('vip_payment_orders', [
'id' => $order->id,
'status' => 'paid',
+ 'provider' => 'wechat',
]);
}
@@ -239,9 +295,14 @@ class VipPaymentIntegrationTest extends TestCase
{
Sysparam::updateOrCreate(['alias' => 'vip_payment_enabled'], ['body' => '1']);
Sysparam::clearCache('vip_payment_enabled');
+ Sysparam::updateOrCreate(['alias' => 'vip_payment_base_url'], ['body' => 'https://novalink.test']);
+ Sysparam::updateOrCreate(['alias' => 'vip_payment_app_key'], ['body' => 'chatroom-app']);
+ Sysparam::updateOrCreate(['alias' => 'vip_payment_app_secret'], ['body' => 'chatroom-secret']);
+ Sysparam::updateOrCreate(['alias' => 'vip_payment_timeout'], ['body' => '10']);
- config(['services.novalink_payment_center.client_id' => 'chatroom-app']);
- config(['services.novalink_payment_center.client_secret' => 'chatroom-secret']);
- config(['services.novalink_payment_center.base_url' => 'https://novalink.test/api/open/v1']);
+ Sysparam::clearCache('vip_payment_base_url');
+ Sysparam::clearCache('vip_payment_app_key');
+ Sysparam::clearCache('vip_payment_app_secret');
+ Sysparam::clearCache('vip_payment_timeout');
}
}