Files
clinicpro/src/Subscription/Service/SubscriptionService.php
T
hamed 1b171a82f4 feat(payment): add refund and reversal functionality to payment gateways
- Implemented `refund` and `reverse` methods in `PaymentGatewayInterface`.
- Added `PaymentRefundResult` class to handle refund operation results.
- Enhanced `MockGateway` and `SepGateway` to support refund and reversal operations.
- Updated `PaymentManager` to include `refundPayment` and `reversePayment` methods for handling refunds and reversals in transactions.
- Modified `ClinicSubscriptionRepository` and `SubscriptionService` to manage subscriptions during refunds.
- Added admin API endpoints for processing refunds and reversals.
- Updated security headers to allow form actions to the sandbox environment.
- Documented the new refund and reversal features in the API documentation.
2026-07-02 18:45:34 +03:30

133 lines
4.3 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\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);
}
public function hasFeature(string $entityType, int $entityId, string $feature): bool
{
$subscription = $this->getActiveSubscription($entityType, $entityId);
if ($subscription === null) {
return false;
}
return $subscription->getPlan()->hasFeature($feature);
}
public function getSecretaryLimit(string $entityType, int $entityId): int
{
$subscription = $this->getActiveSubscription($entityType, $entityId);
if ($subscription === null) {
return 1;
}
return $subscription->getPlan()->getMaxSecretaries();
}
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);
}
$trialPeriod = $this->periodRepo->findTrialPeriodForPlan($basicPlan);
if ($trialPeriod === null) {
throw new AppException(ErrorCodes::ERR_NOT_FOUND_001, null, 500);
}
$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;
}
}