tenantId(); $query = Subscription::query() ->where('tenant_id', $tenantId) ->with(['plan:id,name,code,price,billing_cycle']); // 상태 필터 if (! empty($params['status'])) { $query->where('status', $params['status']); } // 유효한 구독만 if (! empty($params['valid_only']) && $params['valid_only']) { $query->valid(); } // 만료 예정 (N일 이내) if (! empty($params['expiring_within'])) { $query->expiringWithin((int) $params['expiring_within']); } // 날짜 범위 필터 if (! empty($params['start_date'])) { $query->where('started_at', '>=', $params['start_date']); } if (! empty($params['end_date'])) { $query->where('started_at', '<=', $params['end_date']); } // 정렬 $sortBy = $params['sort_by'] ?? 'started_at'; $sortDir = $params['sort_dir'] ?? 'desc'; $query->orderBy($sortBy, $sortDir); $perPage = $params['per_page'] ?? 20; return $query->paginate($perPage); } /** * 현재 활성 구독 */ public function current(): ?Subscription { $tenantId = $this->tenantId(); return Subscription::query() ->where('tenant_id', $tenantId) ->valid() ->with(['plan', 'payments' => function ($q) { $q->completed()->orderBy('paid_at', 'desc')->limit(5); }]) ->orderBy('started_at', 'desc') ->first(); } /** * 구독 상세 */ public function show(int $id): Subscription { $tenantId = $this->tenantId(); return Subscription::query() ->where('tenant_id', $tenantId) ->with([ 'plan', 'payments' => function ($q) { $q->orderBy('paid_at', 'desc'); }, ]) ->findOrFail($id); } // ========================================================================= // 구독 생성/취소 // ========================================================================= /** * 구독 생성 (결제 포함) */ public function store(array $data): Subscription { $tenantId = $this->tenantId(); $userId = $this->apiUserId(); // 요금제 확인 $plan = Plan::active()->findOrFail($data['plan_id']); // 이미 활성 구독이 있는지 확인 $existingSubscription = Subscription::query() ->where('tenant_id', $tenantId) ->valid() ->first(); if ($existingSubscription) { throw new BadRequestHttpException(__('error.subscription.already_active')); } return DB::transaction(function () use ($data, $plan, $tenantId, $userId) { // 구독 생성 $subscription = Subscription::create([ 'tenant_id' => $tenantId, 'plan_id' => $plan->id, 'started_at' => $data['started_at'] ?? now(), 'status' => Subscription::STATUS_PENDING, 'created_by' => $userId, 'updated_by' => $userId, ]); // 결제 생성 (무료 요금제가 아닌 경우) if ($plan->price > 0) { $payment = Payment::create([ 'subscription_id' => $subscription->id, 'amount' => $plan->price, 'payment_method' => $data['payment_method'] ?? Payment::METHOD_CARD, 'status' => Payment::STATUS_PENDING, 'created_by' => $userId, 'updated_by' => $userId, ]); // 결제 완료 처리 (실제 PG 연동 시 수정 필요) if (! empty($data['auto_complete']) && $data['auto_complete']) { $payment->complete($data['transaction_id'] ?? null); // 구독 활성화 $subscription->activate(); } } else { // 무료 요금제는 바로 활성화 Payment::create([ 'subscription_id' => $subscription->id, 'amount' => 0, 'payment_method' => Payment::METHOD_FREE, 'status' => Payment::STATUS_COMPLETED, 'paid_at' => now(), 'created_by' => $userId, 'updated_by' => $userId, ]); $subscription->activate(); } return $subscription->fresh(['plan', 'payments']); }); } /** * 구독 취소 */ public function cancel(int $id, ?string $reason = null): Subscription { $tenantId = $this->tenantId(); $userId = $this->apiUserId(); $subscription = Subscription::query() ->where('tenant_id', $tenantId) ->findOrFail($id); if (! $subscription->isCancellable()) { throw new BadRequestHttpException(__('error.subscription.not_cancellable')); } $subscription->cancel($reason); $subscription->updated_by = $userId; $subscription->save(); return $subscription->fresh(['plan']); } /** * 구독 갱신 */ public function renew(int $id, array $data = []): Subscription { $tenantId = $this->tenantId(); $userId = $this->apiUserId(); $subscription = Subscription::query() ->where('tenant_id', $tenantId) ->findOrFail($id); if ($subscription->status !== Subscription::STATUS_ACTIVE) { throw new BadRequestHttpException(__('error.subscription.not_renewable')); } return DB::transaction(function () use ($subscription, $data, $userId) { $plan = $subscription->plan; // 결제 생성 if ($plan->price > 0) { $payment = Payment::create([ 'subscription_id' => $subscription->id, 'amount' => $plan->price, 'payment_method' => $data['payment_method'] ?? Payment::METHOD_CARD, 'status' => Payment::STATUS_PENDING, 'created_by' => $userId, 'updated_by' => $userId, ]); // 결제 완료 처리 if (! empty($data['auto_complete']) && $data['auto_complete']) { $payment->complete($data['transaction_id'] ?? null); $subscription->renew(); } } else { // 무료 갱신 Payment::create([ 'subscription_id' => $subscription->id, 'amount' => 0, 'payment_method' => Payment::METHOD_FREE, 'status' => Payment::STATUS_COMPLETED, 'paid_at' => now(), 'created_by' => $userId, 'updated_by' => $userId, ]); $subscription->renew(); } $subscription->updated_by = $userId; $subscription->save(); return $subscription->fresh(['plan', 'payments']); }); } // ========================================================================= // 구독 상태 관리 // ========================================================================= /** * 구독 일시정지 */ public function suspend(int $id): Subscription { $tenantId = $this->tenantId(); $userId = $this->apiUserId(); $subscription = Subscription::query() ->where('tenant_id', $tenantId) ->findOrFail($id); if (! $subscription->suspend()) { throw new BadRequestHttpException(__('error.subscription.not_suspendable')); } $subscription->updated_by = $userId; $subscription->save(); return $subscription->fresh(['plan']); } /** * 구독 재개 */ public function resume(int $id): Subscription { $tenantId = $this->tenantId(); $userId = $this->apiUserId(); $subscription = Subscription::query() ->where('tenant_id', $tenantId) ->findOrFail($id); if (! $subscription->resume()) { throw new BadRequestHttpException(__('error.subscription.not_resumable')); } $subscription->updated_by = $userId; $subscription->save(); return $subscription->fresh(['plan']); } }