chore: init saasshop repo + sql migrations runner + gitee go
This commit is contained in:
138
app/Support/SubscriptionActivationService.php
Normal file
138
app/Support/SubscriptionActivationService.php
Normal file
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
|
||||
namespace App\Support;
|
||||
|
||||
use App\Models\PlatformOrder;
|
||||
use App\Models\SiteSubscription;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* 订阅激活服务(最小闭环)
|
||||
*
|
||||
* 目标:当平台订单满足“已支付 + 已生效”时,让站点订阅真正生效或续期。
|
||||
*
|
||||
* 约束:
|
||||
* - 只负责业务层状态/时间的联动,不负责支付回执、异步通知等入口。
|
||||
* - 支持两种情况:
|
||||
* 1) 订单已绑定 site_subscription_id => 续期/延长该订阅
|
||||
* 2) 未绑定 => 创建新订阅并回写订单 site_subscription_id
|
||||
*/
|
||||
class SubscriptionActivationService
|
||||
{
|
||||
/**
|
||||
* @throws ModelNotFoundException
|
||||
*/
|
||||
public function activateOrder(int $orderId, ?int $adminId = null): SiteSubscription
|
||||
{
|
||||
/** @var PlatformOrder $order */
|
||||
$order = PlatformOrder::query()->findOrFail($orderId);
|
||||
|
||||
// 最小校验:必须已支付 + 已生效
|
||||
if ($order->payment_status !== 'paid' || $order->status !== 'activated') {
|
||||
throw new \InvalidArgumentException('订单未满足生效条件:需 payment_status=paid 且 status=activated');
|
||||
}
|
||||
|
||||
$now = now();
|
||||
$months = max(1, (int) $order->period_months) * max(1, (int) ($order->quantity ?? 1));
|
||||
|
||||
// 幂等保护:若该订单已同步过订阅,则直接返回对应订阅(避免重复点击导致无限续期)
|
||||
$activationMeta = (array) data_get($order->meta ?? [], 'subscription_activation', []);
|
||||
$activatedSubscriptionId = (int) ($activationMeta['subscription_id'] ?? 0);
|
||||
if ($activatedSubscriptionId > 0) {
|
||||
$subscription = SiteSubscription::query()->find($activatedSubscriptionId);
|
||||
if ($subscription) {
|
||||
return $subscription;
|
||||
}
|
||||
}
|
||||
|
||||
// 订阅快照:优先使用订单上记录的 plan_name / billing_cycle / plan_snapshot
|
||||
$snapshot = [
|
||||
'from_order_id' => $order->id,
|
||||
'order_no' => $order->order_no,
|
||||
'order_type' => $order->order_type,
|
||||
'plan_snapshot' => $order->plan_snapshot,
|
||||
];
|
||||
|
||||
if ($order->site_subscription_id) {
|
||||
/** @var SiteSubscription $subscription */
|
||||
$subscription = SiteSubscription::query()->findOrFail($order->site_subscription_id);
|
||||
|
||||
// 以 ends_at 为基准续期:
|
||||
// - 若 ends_at 为空或已过期 => 从 now 起算
|
||||
// - 若仍有效 => 从 ends_at 起算
|
||||
$base = $subscription->ends_at && $subscription->ends_at->greaterThan($now)
|
||||
? $subscription->ends_at->copy()
|
||||
: $now->copy();
|
||||
|
||||
$newEndsAt = $base->copy()->addMonthsNoOverflow($months);
|
||||
|
||||
// starts_at:若为空则补齐
|
||||
$startsAt = $subscription->starts_at ?: $now->copy();
|
||||
|
||||
$subscription->fill([
|
||||
'status' => 'activated',
|
||||
'plan_id' => $order->plan_id,
|
||||
'plan_name' => $order->plan_name,
|
||||
'billing_cycle' => $order->billing_cycle,
|
||||
'period_months' => (int) $order->period_months,
|
||||
'amount' => $order->paid_amount > 0 ? $order->paid_amount : $order->payable_amount,
|
||||
'starts_at' => $startsAt,
|
||||
'ends_at' => $newEndsAt,
|
||||
'activated_at' => $subscription->activated_at ?: $now,
|
||||
'snapshot' => array_merge((array) ($subscription->snapshot ?? []), $snapshot),
|
||||
]);
|
||||
$subscription->save();
|
||||
|
||||
// 写入订单 meta,标记该订单已完成订阅同步(幂等)
|
||||
$meta = (array) ($order->meta ?? []);
|
||||
data_set($meta, 'subscription_activation', [
|
||||
'subscription_id' => $subscription->id,
|
||||
'synced_at' => $now->toDateTimeString(),
|
||||
'admin_id' => $adminId,
|
||||
]);
|
||||
$order->meta = $meta;
|
||||
$order->save();
|
||||
|
||||
return $subscription;
|
||||
}
|
||||
|
||||
// 创建新订阅
|
||||
$subscriptionNo = 'SUB' . $now->format('YmdHis') . Str::padLeft((string) random_int(1, 9999), 4, '0');
|
||||
|
||||
$subscription = SiteSubscription::query()->create([
|
||||
'merchant_id' => $order->merchant_id,
|
||||
'plan_id' => $order->plan_id,
|
||||
'status' => 'activated',
|
||||
'source' => 'platform_order',
|
||||
'subscription_no' => $subscriptionNo,
|
||||
'plan_name' => $order->plan_name,
|
||||
'billing_cycle' => $order->billing_cycle,
|
||||
'period_months' => (int) $order->period_months,
|
||||
'amount' => $order->paid_amount > 0 ? $order->paid_amount : $order->payable_amount,
|
||||
'starts_at' => $now,
|
||||
'ends_at' => $now->copy()->addMonthsNoOverflow($months),
|
||||
'activated_at' => $now,
|
||||
'snapshot' => $snapshot,
|
||||
'meta' => [
|
||||
'activated_by_admin_id' => $adminId,
|
||||
],
|
||||
]);
|
||||
|
||||
// 回写订单 + 写入 meta 标记同步完成(幂等)
|
||||
$order->site_subscription_id = $subscription->id;
|
||||
$order->activated_at = $order->activated_at ?: $now;
|
||||
|
||||
$meta = (array) ($order->meta ?? []);
|
||||
data_set($meta, 'subscription_activation', [
|
||||
'subscription_id' => $subscription->id,
|
||||
'synced_at' => $now->toDateTimeString(),
|
||||
'admin_id' => $adminId,
|
||||
]);
|
||||
$order->meta = $meta;
|
||||
|
||||
$order->save();
|
||||
|
||||
return $subscription;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user