续期/延长该订阅 * 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; } }