Files
sam-api/app/Services/PlanService.php
hskwon 45780ea351 feat: 구독/결제 API 확장 (Plan, Subscription, Payment)
- Plan/Subscription/Payment 모델에 상태 상수, 스코프, 헬퍼 메서드 추가
- PlanService, SubscriptionService, PaymentService 생성
- PlanController, SubscriptionController, PaymentController 생성
- FormRequest 9개 생성 (Plan 3개, Subscription 3개, Payment 3개)
- Swagger 문서 3개 생성 (PlanApi, SubscriptionApi, PaymentApi)
- API 라우트 22개 등록 (Plan 7개, Subscription 8개, Payment 7개)
- Pint 코드 스타일 정리
2025-12-18 16:20:29 +09:00

165 lines
4.5 KiB
PHP

<?php
namespace App\Services;
use App\Models\Tenants\Plan;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Database\Eloquent\Collection;
class PlanService extends Service
{
// =========================================================================
// 요금제 목록/상세
// =========================================================================
/**
* 요금제 목록 (관리자용)
*/
public function index(array $params): LengthAwarePaginator
{
$query = Plan::query();
// 활성 상태 필터
if (isset($params['is_active'])) {
$query->where('is_active', (bool) $params['is_active']);
}
// 결제 주기 필터
if (! empty($params['billing_cycle'])) {
$query->ofCycle($params['billing_cycle']);
}
// 검색 (이름, 코드, 설명)
if (! empty($params['search'])) {
$search = $params['search'];
$query->where(function ($q) use ($search) {
$q->where('name', 'like', "%{$search}%")
->orWhere('code', 'like', "%{$search}%")
->orWhere('description', 'like', "%{$search}%");
});
}
// 정렬
$sortBy = $params['sort_by'] ?? 'price';
$sortDir = $params['sort_dir'] ?? 'asc';
$query->orderBy($sortBy, $sortDir);
$perPage = $params['per_page'] ?? 20;
return $query->paginate($perPage);
}
/**
* 활성 요금제 목록 (공개용)
*/
public function active(): Collection
{
return Plan::active()
->orderBy('price', 'asc')
->get();
}
/**
* 요금제 상세
*/
public function show(int $id): Plan
{
return Plan::query()
->withCount(['subscriptions as active_subscriptions_count' => function ($q) {
$q->where('status', 'active');
}])
->findOrFail($id);
}
// =========================================================================
// 요금제 생성/수정/삭제
// =========================================================================
/**
* 요금제 생성
*/
public function store(array $data): Plan
{
$userId = $this->apiUserId();
return Plan::create([
'name' => $data['name'],
'code' => $data['code'],
'description' => $data['description'] ?? null,
'price' => $data['price'],
'billing_cycle' => $data['billing_cycle'] ?? Plan::BILLING_MONTHLY,
'features' => $data['features'] ?? null,
'is_active' => $data['is_active'] ?? true,
'created_by' => $userId,
'updated_by' => $userId,
]);
}
/**
* 요금제 수정
*/
public function update(int $id, array $data): Plan
{
$userId = $this->apiUserId();
$plan = Plan::findOrFail($id);
$plan->fill([
'name' => $data['name'] ?? $plan->name,
'code' => $data['code'] ?? $plan->code,
'description' => $data['description'] ?? $plan->description,
'price' => $data['price'] ?? $plan->price,
'billing_cycle' => $data['billing_cycle'] ?? $plan->billing_cycle,
'features' => $data['features'] ?? $plan->features,
'is_active' => $data['is_active'] ?? $plan->is_active,
'updated_by' => $userId,
]);
$plan->save();
return $plan->fresh();
}
/**
* 요금제 삭제
*/
public function destroy(int $id): bool
{
$userId = $this->apiUserId();
$plan = Plan::findOrFail($id);
// 활성 구독이 있으면 삭제 불가
$activeCount = $plan->subscriptions()
->whereIn('status', ['active', 'pending'])
->count();
if ($activeCount > 0) {
throw new \Symfony\Component\HttpKernel\Exception\BadRequestHttpException(
__('error.plan.has_active_subscriptions')
);
}
$plan->deleted_by = $userId;
$plan->save();
$plan->delete();
return true;
}
/**
* 요금제 활성/비활성 토글
*/
public function toggle(int $id): Plan
{
$userId = $this->apiUserId();
$plan = Plan::findOrFail($id);
$plan->is_active = ! $plan->is_active;
$plan->updated_by = $userId;
$plan->save();
return $plan;
}
}