post.rendered 过滤原只在无权限时替换 [paid];有权限时原始标签直接显示在页面。现在: - 有权限:替换为 <div class="paid-content"> 付费内容块(带「付费内容」小标签),三套主题补齐样式 - 先去掉 Markdown 转换产生的 <p>[paid]</p> 包裹,避免替换后残留未闭合标签 - 测试:游客与会员均断言不出现裸 [paid]
260 lines
9.2 KiB
PHP
260 lines
9.2 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature;
|
|
|
|
use App\Models\Post;
|
|
use App\Models\Setting;
|
|
use App\Models\User;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Plugins\Neatstudio\Membership\Models\MembershipPlan;
|
|
use Plugins\Neatstudio\Membership\Models\Subscription;
|
|
use Plugins\Neatstudio\Payment\Models\Payment;
|
|
use Tests\TestCase;
|
|
|
|
class MembershipFlowTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
private User $user;
|
|
|
|
private MembershipPlan $plan;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
|
|
$this->seed();
|
|
Setting::set('pay_sandbox', '1');
|
|
|
|
$this->user = User::create([
|
|
'name' => '测试会员',
|
|
'email' => 'member@test.com',
|
|
'password' => bcrypt('password'),
|
|
]);
|
|
|
|
$this->plan = MembershipPlan::create([
|
|
'name' => '月付会员',
|
|
'slug' => 'monthly',
|
|
'price' => 9900,
|
|
'duration_days' => 30,
|
|
'permissions' => ['read_members_only'],
|
|
'active' => true,
|
|
]);
|
|
}
|
|
|
|
public function test_membership_page_lists_plans(): void
|
|
{
|
|
$this->get('/membership')->assertOk()->assertSee('月付会员');
|
|
}
|
|
|
|
public function test_subscribe_creates_payment_and_redirects(): void
|
|
{
|
|
$this->actingAs($this->user)
|
|
->post('/membership/'.$this->plan->id.'/subscribe')
|
|
->assertRedirect();
|
|
|
|
$payment = Payment::query()->where('user_id', $this->user->id)->first();
|
|
$this->assertNotNull($payment);
|
|
$this->assertSame('pending', $payment->status);
|
|
$this->assertTrue($payment->payable->is($this->plan));
|
|
$this->assertNull($payment->description);
|
|
}
|
|
|
|
public function test_sandbox_payment_activates_subscription(): void
|
|
{
|
|
$this->actingAs($this->user)->post('/membership/'.$this->plan->id.'/subscribe');
|
|
$payment = Payment::query()->where('user_id', $this->user->id)->first();
|
|
|
|
$this->actingAs($this->user)->get('/pay/checkout/'.$payment->id)->assertRedirect();
|
|
$this->actingAs($this->user)->post('/pay/sandbox/'.$payment->order_no.'/confirm')->assertRedirect();
|
|
|
|
$payment->refresh();
|
|
$this->assertSame('paid', $payment->status);
|
|
|
|
$subscription = Subscription::query()->where('user_id', $this->user->id)->first();
|
|
$this->assertNotNull($subscription);
|
|
$this->assertTrue($subscription->isActive());
|
|
}
|
|
|
|
public function test_paid_content_hidden_for_guest_and_visible_for_member(): void
|
|
{
|
|
$post = Post::create([
|
|
'title' => '付费文章',
|
|
'slug' => 'paid-post',
|
|
'content' => "公开内容\n\n[paid]\n付费隐藏内容\n[/paid]",
|
|
'content_format' => 'markdown',
|
|
'status' => 'published',
|
|
'published_at' => now(),
|
|
]);
|
|
|
|
// 游客:付费部分被替换为 teaser,且不出现裸 [paid] 标签
|
|
$this->get('/posts/paid-post.shtml')
|
|
->assertOk()
|
|
->assertDontSee('付费隐藏内容')
|
|
->assertDontSee('[paid]')
|
|
->assertSee('本文包含付费内容')
|
|
->assertSee('登录后查看');
|
|
|
|
// 会员:可见,且 [paid] 标签被替换为付费内容块(不裸露)
|
|
$this->actingAs($this->user)->post('/membership/'.$this->plan->id.'/subscribe');
|
|
$payment = Payment::query()->where('user_id', $this->user->id)->first();
|
|
$this->actingAs($this->user)->post('/pay/sandbox/'.$payment->order_no.'/confirm');
|
|
|
|
$this->actingAs($this->user)->get('/posts/paid-post.shtml')
|
|
->assertOk()
|
|
->assertSee('付费隐藏内容')
|
|
->assertSee('paid-content')
|
|
->assertDontSee('[paid]');
|
|
}
|
|
|
|
public function test_unlock_post_creates_payment_with_payable_and_grants_access(): void
|
|
{
|
|
$post = Post::create([
|
|
'title' => '单篇付费',
|
|
'slug' => 'single-paid',
|
|
'content' => '付费内容正文',
|
|
'content_format' => 'markdown',
|
|
'status' => 'published',
|
|
'published_at' => now(),
|
|
// members_only:非会员被拦截,可单篇支付解锁
|
|
'meta' => ['price' => 500, 'members_only' => true],
|
|
]);
|
|
|
|
$this->actingAs($this->user)
|
|
->post('/posts/'.$post->id.'/unlock')
|
|
->assertRedirect();
|
|
|
|
$payment = Payment::query()->where('user_id', $this->user->id)->first();
|
|
$this->assertNotNull($payment);
|
|
$this->assertTrue($payment->payable->is($post));
|
|
$this->assertSame(500, $payment->amount);
|
|
$this->assertStringContainsString('单篇付费', $payment->payable_label);
|
|
$this->assertNotNull($payment->payable_url);
|
|
|
|
// 沙箱支付后写入解锁用户
|
|
$this->actingAs($this->user)->post('/pay/sandbox/'.$payment->order_no.'/confirm');
|
|
|
|
$post->refresh();
|
|
$this->assertContains($this->user->id, $post->meta['unlocked_user_ids']);
|
|
}
|
|
|
|
public function test_legacy_order_without_payable_shows_placeholder(): void
|
|
{
|
|
$payment = Payment::create([
|
|
'order_no' => 'LEGACY0001',
|
|
'user_id' => $this->user->id,
|
|
'subject' => '历史订单',
|
|
'amount' => 100,
|
|
'status' => 'paid',
|
|
'paid_at' => now(),
|
|
]);
|
|
|
|
$this->assertSame('—', $payment->payable_label);
|
|
$this->assertNull($payment->payable_url);
|
|
}
|
|
|
|
public function test_unlock_post_skips_payment_when_already_unlocked(): void
|
|
{
|
|
$post = Post::create([
|
|
'title' => '已解锁',
|
|
'slug' => 'already-unlocked',
|
|
'content' => '内容',
|
|
'content_format' => 'markdown',
|
|
'status' => 'published',
|
|
'published_at' => now(),
|
|
'meta' => ['price' => 500, 'unlocked_user_ids' => [$this->user->id]],
|
|
]);
|
|
|
|
$this->actingAs($this->user)
|
|
->post('/posts/'.$post->id.'/unlock')
|
|
->assertRedirect('/posts/already-unlocked.shtml');
|
|
|
|
$this->assertSame(0, Payment::query()->count());
|
|
}
|
|
|
|
public function test_subscribe_skips_payment_when_plan_already_active(): void
|
|
{
|
|
Subscription::create([
|
|
'user_id' => $this->user->id,
|
|
'membership_plan_id' => $this->plan->id,
|
|
'status' => 'active',
|
|
'starts_at' => now(),
|
|
'ends_at' => now()->addDays(30),
|
|
]);
|
|
|
|
$this->actingAs($this->user)
|
|
->post('/membership/'.$this->plan->id.'/subscribe')
|
|
->assertRedirect(route('membership.mine'));
|
|
|
|
$this->assertSame(0, Payment::query()->count());
|
|
}
|
|
|
|
public function test_unlock_paid_block_creates_payment_for_non_member(): void
|
|
{
|
|
$post = Post::create([
|
|
'title' => '付费块文章',
|
|
'slug' => 'paid-block-unlock',
|
|
'content' => "开头\n\n[paid]\n隐藏付费块\n[/paid]\n\n结尾",
|
|
'content_format' => 'markdown',
|
|
'status' => 'published',
|
|
'published_at' => now(),
|
|
'meta' => ['price' => 500],
|
|
]);
|
|
|
|
// 付费块文章的整篇公开,但 [paid] 块不可读——解锁必须创建订单,不能误判为"已有权限"
|
|
$this->actingAs($this->user)
|
|
->post('/posts/'.$post->id.'/unlock')
|
|
->assertRedirect(route('pay.checkout', Payment::first()));
|
|
|
|
$payment = Payment::query()->where('user_id', $this->user->id)->first();
|
|
$this->assertNotNull($payment);
|
|
$this->assertTrue($payment->payable->is($post));
|
|
$this->assertSame('pending', $payment->status);
|
|
|
|
// 沙箱支付后 [paid] 块可见
|
|
$this->actingAs($this->user)->post('/pay/sandbox/'.$payment->order_no.'/confirm');
|
|
$this->actingAs($this->user)->get('/posts/paid-block-unlock.shtml')
|
|
->assertOk()
|
|
->assertSee('隐藏付费块');
|
|
}
|
|
|
|
public function test_members_only_post_shows_paywall_to_guest(): void
|
|
{
|
|
$post = Post::create([
|
|
'title' => '会员专享文章',
|
|
'slug' => 'members-only-post',
|
|
'content' => '这是会员才能看到的正文内容',
|
|
'content_format' => 'markdown',
|
|
'status' => 'published',
|
|
'published_at' => now(),
|
|
'meta' => ['members_only' => true, 'price' => 500],
|
|
]);
|
|
|
|
$this->get('/posts/members-only-post.shtml')
|
|
->assertOk()
|
|
->assertSee('登录后解锁')
|
|
->assertSee('单篇解锁')
|
|
->assertSee('会员专享文章')
|
|
->assertDontSee('这是会员才能看到的正文内容');
|
|
}
|
|
|
|
public function test_members_only_post_paywall_shows_unlock_buttons_to_user(): void
|
|
{
|
|
$post = Post::create([
|
|
'title' => '会员专享文章2',
|
|
'slug' => 'members-only-post-2',
|
|
'content' => '正文内容不可见',
|
|
'content_format' => 'markdown',
|
|
'status' => 'published',
|
|
'published_at' => now(),
|
|
'meta' => ['members_only' => true, 'price' => 9900],
|
|
]);
|
|
|
|
$this->actingAs($this->user)->get('/posts/members-only-post-2.shtml')
|
|
->assertOk()
|
|
->assertSee('单篇解锁')
|
|
->assertSee('开通会员')
|
|
->assertDontSee('正文内容不可见');
|
|
}
|
|
}
|