feat: add vip payment and member center

This commit is contained in:
2026-04-11 12:01:52 +08:00
parent db26820544
commit 746116d325
23 changed files with 1781 additions and 2 deletions
@@ -0,0 +1,189 @@
<?php
/**
* 文件功能:VIP 支付集成功能测试
* 用于验证聊天室发起 VIP 支付下单与接收异步回调开通会员的关键链路
*/
namespace Tests\Feature\Feature;
use App\Models\Sysparam;
use App\Models\User;
use App\Models\VipLevel;
use App\Models\VipPaymentOrder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Http;
use Tests\TestCase;
class VipPaymentIntegrationTest extends TestCase
{
use RefreshDatabase;
/**
* 测试用户可以发起 VIP 支付并跳转到支付中心页面
*/
public function test_user_can_create_vip_payment_order_and_redirect_to_payment_center(): 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' => 'PO202604111200001234',
'pay_url' => 'https://novalink.test/payment/checkout/alipay/PO202604111200001234',
],
], 201),
]);
$response = $this->actingAs($user)->post(route('vip.payment.store'), [
'vip_level_id' => $vipLevel->id,
]);
$response->assertRedirect('https://novalink.test/payment/checkout/alipay/PO202604111200001234');
$this->assertDatabaseHas('vip_payment_orders', [
'user_id' => $user->id,
'vip_level_id' => $vipLevel->id,
'payment_order_no' => 'PO202604111200001234',
'status' => 'pending',
]);
}
/**
* 测试异步回调只会为同一笔订单开通一次会员,避免重复叠加时长
*/
public function test_async_notify_opens_vip_only_once(): void
{
$this->seedVipPaymentConfig();
$user = User::factory()->create();
$vipLevel = VipLevel::factory()->create([
'name' => '钻石会员',
'price' => 88,
'duration_days' => 30,
]);
$order = VipPaymentOrder::factory()->create([
'user_id' => $user->id,
'vip_level_id' => $vipLevel->id,
'amount' => 88.00,
'vip_name' => $vipLevel->name,
'vip_duration_days' => $vipLevel->duration_days,
'payment_order_no' => 'PO202604111530001234',
'merchant_order_no' => 'VPO202604111530001234',
]);
$payload = [
'payment_order_no' => $order->payment_order_no,
'merchant_order_no' => $order->merchant_order_no,
'status' => 'paid',
'amount' => '88.00',
'provider' => 'alipay',
'provider_trade_no' => '2026041122001499999999999999',
'paid_at' => '2026-04-11 15:35:12',
];
$rawBody = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
$signature = hash_hmac('sha256', $rawBody, 'chatroom-secret');
$firstResponse = $this->call(
'POST',
route('vip.payment.notify'),
[],
[],
[],
[
'CONTENT_TYPE' => 'application/json',
'HTTP_X_PAYMENT_SIGNATURE' => $signature,
],
$rawBody
);
$firstResponse->assertOk();
$user->refresh();
$this->assertSame($vipLevel->id, $user->vip_level_id);
$this->assertNotNull($user->hy_time);
$firstExpireAt = $user->hy_time?->toDateTimeString();
$secondResponse = $this->call(
'POST',
route('vip.payment.notify'),
[],
[],
[],
[
'CONTENT_TYPE' => 'application/json',
'HTTP_X_PAYMENT_SIGNATURE' => $signature,
],
$rawBody
);
$secondResponse->assertOk();
$user->refresh();
$this->assertSame($firstExpireAt, $user->hy_time?->toDateTimeString());
$this->assertDatabaseHas('vip_payment_orders', [
'id' => $order->id,
'status' => 'paid',
]);
}
/**
* 测试会员中心页面只展示当前用户自己的购买记录
*/
public function test_vip_center_only_shows_current_user_records(): void
{
$currentUser = User::factory()->create(['username' => 'current-user']);
$otherUser = User::factory()->create(['username' => 'other-user']);
$vipLevel = VipLevel::factory()->create([
'name' => '星耀会员',
'price' => 66,
'duration_days' => 30,
]);
VipPaymentOrder::factory()->create([
'user_id' => $currentUser->id,
'vip_level_id' => $vipLevel->id,
'vip_name' => $vipLevel->name,
'status' => 'paid',
'order_no' => 'VPO_CURRENT_001',
'merchant_order_no' => 'MER_CURRENT_001',
]);
VipPaymentOrder::factory()->create([
'user_id' => $otherUser->id,
'vip_level_id' => $vipLevel->id,
'vip_name' => $vipLevel->name,
'status' => 'paid',
'order_no' => 'VPO_OTHER_001',
'merchant_order_no' => 'MER_OTHER_001',
]);
$response = $this->actingAs($currentUser)->get(route('vip.center'));
$response->assertOk();
$response->assertSee('VPO_CURRENT_001');
$response->assertDontSee('VPO_OTHER_001');
$response->assertSee('我的购买记录');
}
/**
* 写入测试所需的支付中心配置
*/
private function seedVipPaymentConfig(): void
{
// 这些配置与聊天室后台保存的数据结构保持一致,方便直接复用真实业务代码。
Sysparam::query()->updateOrCreate(['alias' => 'vip_payment_enabled'], ['body' => '1']);
Sysparam::query()->updateOrCreate(['alias' => 'vip_payment_base_url'], ['body' => 'https://novalink.test']);
Sysparam::query()->updateOrCreate(['alias' => 'vip_payment_app_key'], ['body' => 'chatroom-app']);
Sysparam::query()->updateOrCreate(['alias' => 'vip_payment_app_secret'], ['body' => 'chatroom-secret']);
Sysparam::query()->updateOrCreate(['alias' => 'vip_payment_timeout'], ['body' => '10']);
}
}