feat: Payment 订单商品化——多态 payable 关联购买实体

- payments 表新增 payable_type/payable_id(nullableMorphs),订单可关联文章/套餐等可购买实体
- createOrder 接收 $payable 模型,取代 description 字符串解析
- membership 支付回调按 payable instanceof 分发(激活订阅 / 解锁单篇)
- Post/MembershipPlan 提供 payableLabel/payableUrl 约定方法;Payment 用约定方法生成展示标签与后台链接(皮肤等新实体可直接接入)
- 订单表格新增「类型」徽章(文章/会员套餐),商品列链接到对应后台编辑页;无 payable 的旧订单显示 —
This commit is contained in:
ak
2026-08-12 00:22:50 +08:00
parent 4f59afb7ce
commit ec087fe2a8
9 changed files with 178 additions and 36 deletions
@@ -43,8 +43,8 @@ class MembershipController
$user,
'会员套餐:'.$plan->name,
$plan->price,
$channel,
'MembershipPlan#'.$plan->id
$plan,
$channel
);
return redirect()->route('pay.checkout', $payment);
@@ -77,8 +77,8 @@ class MembershipController
$user,
'解锁文章:'.$post->title,
$price,
$channel,
'PostUnlock#'.$post->id
$post,
$channel
);
return redirect()->route('pay.checkout', $payment);
@@ -23,4 +23,17 @@ class MembershipPlan extends Model
{
return $this->hasMany(Subscription::class);
}
/**
* 作为可购买实体的展示约定。
*/
public function payableLabel(): string
{
return '会员套餐:'.$this->name;
}
public function payableUrl(): ?string
{
return route('filament.admin.resources.membership-plans.edit', $this);
}
}
@@ -7,6 +7,7 @@ namespace Plugins\Neatstudio\Membership;
use App\Blog\Support\PluginManager;
use App\Blog\Support\PluginServiceProvider;
use App\Models\Post;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle;
use Filament\Schemas\Components\Section;
@@ -14,7 +15,6 @@ use Filament\Schemas\Schema;
use Filament\Tables\Columns\IconColumn;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
use Illuminate\Support\Str;
use Plugins\Neatstudio\Membership\Models\MembershipPlan;
use Plugins\Neatstudio\Membership\Models\Subscription;
use Plugins\Neatstudio\Payment\Models\Payment;
@@ -25,41 +25,31 @@ class ServiceProvider extends PluginServiceProvider
{
$this->loadRoutes(__DIR__.'/../routes/web.php');
// 支付成功:激活会员订阅 / 解锁单篇付费文章
// 支付成功:按购买实体分发(激活会员订阅 / 解锁单篇付费文章
$manager->addAction('payment.paid', function (Payment $payment) {
$description = (string) $payment->description;
$user = $payment->user;
$payable = $payment->payable;
if (! $user) {
if (! $user || ! $payable) {
return;
}
if (str_starts_with($description, 'MembershipPlan#')) {
$planId = (int) substr($description, strlen('MembershipPlan#'));
$plan = MembershipPlan::find($planId);
if ($plan) {
$now = now();
Subscription::create([
'user_id' => $user->id,
'membership_plan_id' => $plan->id,
'status' => 'active',
'starts_at' => $now,
'ends_at' => $now->copy()->addDays($plan->duration_days),
]);
}
if ($payable instanceof MembershipPlan) {
$now = now();
Subscription::create([
'user_id' => $user->id,
'membership_plan_id' => $payable->id,
'status' => 'active',
'starts_at' => $now,
'ends_at' => $now->copy()->addDays($payable->duration_days),
]);
}
if (str_starts_with($description, 'PostUnlock#')) {
$postId = (int) substr($description, strlen('PostUnlock#'));
$post = \App\Models\Post::find($postId);
if ($post) {
$meta = $post->meta ?? [];
$unlocked = $meta['unlocked_user_ids'] ?? [];
$unlocked[] = $user->id;
$post->update(['meta' => array_merge($meta, ['unlocked_user_ids' => array_values(array_unique($unlocked))])]);
}
if ($payable instanceof Post) {
$meta = $payable->meta ?? [];
$unlocked = $meta['unlocked_user_ids'] ?? [];
$unlocked[] = $user->id;
$payable->update(['meta' => array_merge($meta, ['unlocked_user_ids' => array_values(array_unique($unlocked))])]);
}
}, 10);