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:
+24
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('payments', function (Blueprint $table) {
|
||||
$table->nullableMorphs('payable');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('payments', function (Blueprint $table) {
|
||||
$table->dropMorphs('payable');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -42,7 +42,22 @@ class PaymentResource extends Resource
|
||||
->columns([
|
||||
\Filament\Tables\Columns\TextColumn::make('order_no')->label('订单号')->searchable(),
|
||||
\Filament\Tables\Columns\TextColumn::make('user.name')->label('用户'),
|
||||
\Filament\Tables\Columns\TextColumn::make('subject')->label('商品')->limit(30),
|
||||
\Filament\Tables\Columns\TextColumn::make('subject')->label('商品')
|
||||
->limit(30)
|
||||
->url(fn (Payment $record) => $record->payable_url),
|
||||
\Filament\Tables\Columns\TextColumn::make('payable_type')->label('类型')
|
||||
->badge()
|
||||
->placeholder('—')
|
||||
->formatStateUsing(fn ($state) => match (class_basename((string) $state)) {
|
||||
'Post' => '文章',
|
||||
'MembershipPlan' => '会员套餐',
|
||||
default => $state ? class_basename((string) $state) : '—',
|
||||
})
|
||||
->color(fn ($state) => match (class_basename((string) $state)) {
|
||||
'Post' => 'info',
|
||||
'MembershipPlan' => 'success',
|
||||
default => 'gray',
|
||||
}),
|
||||
\Filament\Tables\Columns\TextColumn::make('amount')->label('金额')
|
||||
->formatStateUsing(fn ($state) => '¥'.number_format((float) $state / 100, 2)),
|
||||
\Filament\Tables\Columns\TextColumn::make('channel')->label('渠道')->badge(),
|
||||
@@ -61,7 +76,6 @@ class PaymentResource extends Resource
|
||||
default => 'gray',
|
||||
}),
|
||||
\Filament\Tables\Columns\TextColumn::make('paid_at')->label('支付时间')->dateTime('Y-m-d H:i'),
|
||||
\Filament\Tables\Columns\TextColumn::make('created_at')->label('创建时间')->dateTime('Y-m-d H:i'),
|
||||
])
|
||||
->filters([
|
||||
\Filament\Tables\Filters\SelectFilter::make('status')
|
||||
|
||||
@@ -8,11 +8,13 @@ namespace Plugins\Neatstudio\Payment\Models;
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||
|
||||
class Payment extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'order_no', 'user_id', 'subject', 'description', 'amount', 'channel', 'status', 'gateway_trade_no', 'payload', 'paid_at',
|
||||
'payable_type', 'payable_id',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
@@ -25,6 +27,40 @@ class Payment extends Model
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 可购买实体:文章(单篇解锁)、会员套餐、皮肤等。
|
||||
*/
|
||||
public function payable(): MorphTo
|
||||
{
|
||||
return $this->morphTo();
|
||||
}
|
||||
|
||||
public function getPayableLabelAttribute(): string
|
||||
{
|
||||
$payable = $this->payable;
|
||||
|
||||
if (! $payable) {
|
||||
return '—';
|
||||
}
|
||||
|
||||
if (method_exists($payable, 'payableLabel')) {
|
||||
return $payable->payableLabel();
|
||||
}
|
||||
|
||||
return $payable->title ?? $payable->name ?? ('#'.$payable->getKey());
|
||||
}
|
||||
|
||||
public function getPayableUrlAttribute(): ?string
|
||||
{
|
||||
$payable = $this->payable;
|
||||
|
||||
if (! $payable || ! method_exists($payable, 'payableUrl')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $payable->payableUrl();
|
||||
}
|
||||
|
||||
public function markPaid(string $gatewayTradeNo = null): void
|
||||
{
|
||||
$this->update([
|
||||
|
||||
@@ -7,6 +7,7 @@ namespace Plugins\Neatstudio\Payment\Services;
|
||||
|
||||
use App\Models\Setting;
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Str;
|
||||
use Plugins\Neatstudio\Payment\Models\Payment;
|
||||
use Yansongda\Pay\Pay;
|
||||
@@ -15,7 +16,10 @@ use Yansongda\Pay\Provider\Wechat;
|
||||
|
||||
class PaymentManager
|
||||
{
|
||||
public function createOrder(User $user, string $subject, int $amountCents, string $channel = 'alipay', string $description = null): Payment
|
||||
/**
|
||||
* 创建订单。$payable 为可购买实体(文章/套餐/皮肤),支付成功后经 payment.paid 钩子分发。
|
||||
*/
|
||||
public function createOrder(User $user, string $subject, int $amountCents, ?Model $payable = null, string $channel = 'alipay', string $description = null): Payment
|
||||
{
|
||||
$orderNo = date('YmdHis').Str::random(8);
|
||||
|
||||
@@ -27,6 +31,8 @@ class PaymentManager
|
||||
'amount' => $amountCents,
|
||||
'channel' => $channel,
|
||||
'status' => 'pending',
|
||||
'payable_type' => $payable?->getMorphClass(),
|
||||
'payable_id' => $payable?->getKey(),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user