141 lines
4.8 KiB
PHP
141 lines
4.8 KiB
PHP
<?php
|
|
|
|
namespace App\Subscription\Service;
|
|
|
|
use App\Config\Repository\SiteConfigRepository;
|
|
use App\Payment\Entity\Payment;
|
|
use App\Shared\Constant\ErrorCodes;
|
|
use App\Shared\Exception\AppException;
|
|
use App\Subscription\Entity\ClinicSubscription;
|
|
use App\Subscription\Entity\SubscriptionPlan;
|
|
use App\Subscription\Repository\ClinicSubscriptionRepository;
|
|
use App\Subscription\Repository\SubscriptionPeriodRepository;
|
|
use App\Subscription\Repository\SubscriptionPlanRepository;
|
|
|
|
class SubscriptionService
|
|
{
|
|
public function __construct(
|
|
private readonly ClinicSubscriptionRepository $subscriptionRepo,
|
|
private readonly SubscriptionPlanRepository $planRepo,
|
|
private readonly SubscriptionPeriodRepository $periodRepo,
|
|
private readonly SiteConfigRepository $configRepo,
|
|
) {}
|
|
|
|
public function getActiveSubscription(string $entityType, int $entityId): ?ClinicSubscription
|
|
{
|
|
return $this->subscriptionRepo->findActive($entityType, $entityId);
|
|
}
|
|
|
|
/**
|
|
* پلن مؤثر: پلن اشتراک فعال، یا در نبود اشتراک، پلن پیشفرض «free».
|
|
*/
|
|
public function getEffectivePlan(string $entityType, int $entityId): ?SubscriptionPlan
|
|
{
|
|
$subscription = $this->getActiveSubscription($entityType, $entityId);
|
|
|
|
return $subscription?->getPlan() ?? $this->planRepo->findByName('free');
|
|
}
|
|
|
|
public function hasFeature(string $entityType, int $entityId, string $feature): bool
|
|
{
|
|
$plan = $this->getEffectivePlan($entityType, $entityId);
|
|
|
|
return $plan !== null && $plan->hasFeature($feature);
|
|
}
|
|
|
|
public function getSecretaryLimit(string $entityType, int $entityId): int
|
|
{
|
|
$plan = $this->getEffectivePlan($entityType, $entityId);
|
|
|
|
return $plan?->getMaxSecretaries() ?? 1;
|
|
}
|
|
|
|
public function hasUsedTrial(string $entityType, int $entityId): bool
|
|
{
|
|
return $this->subscriptionRepo->hasUsedTrial($entityType, $entityId);
|
|
}
|
|
|
|
public function activateTrial(string $entityType, int $entityId): ClinicSubscription
|
|
{
|
|
if ($this->hasUsedTrial($entityType, $entityId)) {
|
|
throw new AppException(ErrorCodes::ERR_TRIAL_ALREADY_USED, null, 422);
|
|
}
|
|
|
|
$trialEnabled = $this->configRepo->get('trial_enabled');
|
|
if ($trialEnabled === '0') {
|
|
throw new AppException(ErrorCodes::ERR_TRIAL_DISABLED, null, 422);
|
|
}
|
|
|
|
$basicPlan = $this->planRepo->findByName('basic');
|
|
if ($basicPlan === null) {
|
|
throw new AppException(ErrorCodes::ERR_NOT_FOUND_001, null, 500);
|
|
}
|
|
|
|
// Deactivating the trial period is a normal admin configuration, not a
|
|
// broken install — surface it as "trial disabled", the same 422 the
|
|
// trial_enabled flag produces, rather than a 500.
|
|
$trialPeriod = $this->periodRepo->findTrialPeriodForPlan($basicPlan);
|
|
if ($trialPeriod === null) {
|
|
throw new AppException(ErrorCodes::ERR_TRIAL_DISABLED, null, 422);
|
|
}
|
|
|
|
$expiresAt = $this->calculateExpiresAt(null, $trialPeriod->getDurationMonths());
|
|
|
|
$subscription = new ClinicSubscription(
|
|
$entityType,
|
|
$entityId,
|
|
$basicPlan,
|
|
$trialPeriod,
|
|
true,
|
|
$expiresAt,
|
|
null
|
|
);
|
|
|
|
$this->subscriptionRepo->save($subscription);
|
|
|
|
return $subscription;
|
|
}
|
|
|
|
public function createFromPayment(Payment $payment, string $entityType, int $entityId, string $periodUuid): ClinicSubscription
|
|
{
|
|
$period = $this->periodRepo->findByUuid($periodUuid);
|
|
if ($period === null) {
|
|
throw new AppException(ErrorCodes::ERR_NOT_FOUND_001, null, 404);
|
|
}
|
|
|
|
$currentSub = $this->getActiveSubscription($entityType, $entityId);
|
|
$currentExpires = $currentSub?->getExpiresAt();
|
|
|
|
$expiresAt = $this->calculateExpiresAt($currentExpires, $period->getDurationMonths());
|
|
|
|
$subscription = new ClinicSubscription(
|
|
$entityType,
|
|
$entityId,
|
|
$period->getPlan(),
|
|
$period,
|
|
false,
|
|
$expiresAt,
|
|
$payment
|
|
);
|
|
|
|
$this->subscriptionRepo->save($subscription);
|
|
|
|
return $subscription;
|
|
}
|
|
|
|
/** حذف اشتراکِ ساختهشده از یک پرداخت (هنگام استرداد/برگشت وجه). */
|
|
public function deleteByPayment(\App\Payment\Entity\Payment $payment): void
|
|
{
|
|
$subscription = $this->subscriptionRepo->findByPayment($payment);
|
|
if ($subscription !== null) {
|
|
$this->subscriptionRepo->remove($subscription);
|
|
}
|
|
}
|
|
|
|
public function calculateExpiresAt(?int $currentExpiresAt, int $durationMonths): int
|
|
{
|
|
$base = max($currentExpiresAt ?? 0, time());
|
|
return $base + $durationMonths * 30 * 86400;
|
|
}
|
|
}
|