205 lines
7.4 KiB
PHP
205 lines
7.4 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Services\Entitlements;
|
|
|
|
use App\Models\Workspace;
|
|
use App\Models\WorkspaceSubscription;
|
|
use App\Services\Settings\SettingsResolver;
|
|
use Carbon\CarbonInterface;
|
|
|
|
final class WorkspaceSubscriptionResolver
|
|
{
|
|
public const SOURCE_WORKSPACE_SUBSCRIPTION = 'workspace_subscription';
|
|
|
|
/**
|
|
* @return array<string, string>
|
|
*/
|
|
public static function stateLabels(): array
|
|
{
|
|
return [
|
|
WorkspaceSubscription::STATE_TRIAL => 'Trial',
|
|
WorkspaceSubscription::STATE_ACTIVE => 'Active',
|
|
WorkspaceSubscription::STATE_PAST_DUE => 'Past due',
|
|
WorkspaceSubscription::STATE_CANCEL_AT_PERIOD_END => 'Cancellation pending',
|
|
WorkspaceSubscription::STATE_ENDED => 'Ended',
|
|
];
|
|
}
|
|
|
|
public function __construct(
|
|
private readonly SettingsResolver $settingsResolver,
|
|
) {}
|
|
|
|
/**
|
|
* @return array{
|
|
* workspace_id: int,
|
|
* subscription_present: bool,
|
|
* state: string|null,
|
|
* label: string|null,
|
|
* billing_reference: string|null,
|
|
* status_reason: string|null,
|
|
* key_date_label: string|null,
|
|
* key_date: CarbonInterface|null,
|
|
* needs_review: bool,
|
|
* source: string,
|
|
* fallback_status: bool,
|
|
* derived_lifecycle_state: string
|
|
* }
|
|
*/
|
|
public function summary(Workspace $workspace): array
|
|
{
|
|
$workspace->loadMissing('subscription');
|
|
|
|
$subscription = $workspace->subscription;
|
|
|
|
if ($subscription instanceof WorkspaceSubscription) {
|
|
return $this->subscriptionSummary($workspace, $subscription);
|
|
}
|
|
|
|
return $this->fallbackSummary($workspace);
|
|
}
|
|
|
|
/**
|
|
* @return array{
|
|
* workspace_id: int,
|
|
* subscription_present: bool,
|
|
* state: string,
|
|
* label: string,
|
|
* billing_reference: string|null,
|
|
* status_reason: string,
|
|
* key_date_label: string|null,
|
|
* key_date: CarbonInterface|null,
|
|
* needs_review: bool,
|
|
* source: string,
|
|
* fallback_status: bool,
|
|
* derived_lifecycle_state: string
|
|
* }
|
|
*/
|
|
private function subscriptionSummary(Workspace $workspace, WorkspaceSubscription $subscription): array
|
|
{
|
|
$state = in_array($subscription->state, WorkspaceSubscription::stateIds(), true)
|
|
? $subscription->state
|
|
: WorkspaceSubscription::STATE_ACTIVE;
|
|
|
|
$keyDate = $this->keyDateForSubscription($subscription, $state);
|
|
|
|
return [
|
|
'workspace_id' => (int) $workspace->getKey(),
|
|
'subscription_present' => true,
|
|
'state' => $state,
|
|
'label' => self::stateLabels()[$state],
|
|
'billing_reference' => $subscription->billing_reference,
|
|
'status_reason' => $subscription->status_reason,
|
|
'key_date_label' => $this->keyDateLabel($state),
|
|
'key_date' => $keyDate,
|
|
'needs_review' => $this->needsReview($state, $keyDate),
|
|
'source' => self::SOURCE_WORKSPACE_SUBSCRIPTION,
|
|
'fallback_status' => false,
|
|
'derived_lifecycle_state' => $this->derivedLifecycleState($state),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @return array{
|
|
* workspace_id: int,
|
|
* subscription_present: bool,
|
|
* state: null,
|
|
* label: null,
|
|
* billing_reference: null,
|
|
* status_reason: string|null,
|
|
* key_date_label: null,
|
|
* key_date: null,
|
|
* needs_review: bool,
|
|
* source: string,
|
|
* fallback_status: bool,
|
|
* derived_lifecycle_state: string
|
|
* }
|
|
*/
|
|
private function fallbackSummary(Workspace $workspace): array
|
|
{
|
|
$stateSetting = $this->settingsResolver->resolveDetailed(
|
|
workspace: $workspace,
|
|
domain: WorkspaceCommercialLifecycleResolver::SETTING_DOMAIN,
|
|
key: WorkspaceCommercialLifecycleResolver::SETTING_COMMERCIAL_LIFECYCLE_STATE,
|
|
);
|
|
|
|
$rawState = is_string($stateSetting['value'] ?? null)
|
|
? strtolower(trim((string) $stateSetting['value']))
|
|
: null;
|
|
|
|
$derivedLifecycleState = in_array($rawState, WorkspaceCommercialLifecycleResolver::stateIds(), true)
|
|
? $rawState
|
|
: WorkspaceCommercialLifecycleResolver::STATE_ACTIVE_PAID;
|
|
|
|
$source = ($stateSetting['source'] ?? null) === 'workspace_override' && $rawState !== null
|
|
? WorkspaceCommercialLifecycleResolver::SOURCE_WORKSPACE_SETTING
|
|
: WorkspaceCommercialLifecycleResolver::SOURCE_DEFAULT_ACTIVE_PAID;
|
|
|
|
$statusReason = $this->settingsResolver->resolveValue(
|
|
workspace: $workspace,
|
|
domain: WorkspaceCommercialLifecycleResolver::SETTING_DOMAIN,
|
|
key: WorkspaceCommercialLifecycleResolver::SETTING_COMMERCIAL_LIFECYCLE_REASON,
|
|
);
|
|
|
|
return [
|
|
'workspace_id' => (int) $workspace->getKey(),
|
|
'subscription_present' => false,
|
|
'state' => null,
|
|
'label' => null,
|
|
'billing_reference' => null,
|
|
'status_reason' => is_string($statusReason) && trim($statusReason) !== '' ? trim($statusReason) : null,
|
|
'key_date_label' => null,
|
|
'key_date' => null,
|
|
'needs_review' => false,
|
|
'source' => $source,
|
|
'fallback_status' => true,
|
|
'derived_lifecycle_state' => $derivedLifecycleState,
|
|
];
|
|
}
|
|
|
|
private function derivedLifecycleState(string $state): string
|
|
{
|
|
return match ($state) {
|
|
WorkspaceSubscription::STATE_TRIAL => WorkspaceCommercialLifecycleResolver::STATE_TRIAL,
|
|
WorkspaceSubscription::STATE_ACTIVE => WorkspaceCommercialLifecycleResolver::STATE_ACTIVE_PAID,
|
|
WorkspaceSubscription::STATE_PAST_DUE => WorkspaceCommercialLifecycleResolver::STATE_GRACE,
|
|
WorkspaceSubscription::STATE_CANCEL_AT_PERIOD_END => WorkspaceCommercialLifecycleResolver::STATE_ACTIVE_PAID,
|
|
WorkspaceSubscription::STATE_ENDED => WorkspaceCommercialLifecycleResolver::STATE_SUSPENDED_READ_ONLY,
|
|
default => WorkspaceCommercialLifecycleResolver::STATE_ACTIVE_PAID,
|
|
};
|
|
}
|
|
|
|
private function keyDateLabel(string $state): ?string
|
|
{
|
|
return match ($state) {
|
|
WorkspaceSubscription::STATE_TRIAL => 'Trial ends',
|
|
WorkspaceSubscription::STATE_ACTIVE,
|
|
WorkspaceSubscription::STATE_PAST_DUE,
|
|
WorkspaceSubscription::STATE_CANCEL_AT_PERIOD_END,
|
|
WorkspaceSubscription::STATE_ENDED => 'Current period ends',
|
|
default => null,
|
|
};
|
|
}
|
|
|
|
private function keyDateForSubscription(WorkspaceSubscription $subscription, string $state): ?CarbonInterface
|
|
{
|
|
return match ($state) {
|
|
WorkspaceSubscription::STATE_TRIAL => $subscription->trial_ends_at,
|
|
WorkspaceSubscription::STATE_ACTIVE,
|
|
WorkspaceSubscription::STATE_PAST_DUE,
|
|
WorkspaceSubscription::STATE_CANCEL_AT_PERIOD_END,
|
|
WorkspaceSubscription::STATE_ENDED => $subscription->current_period_ends_at,
|
|
default => null,
|
|
};
|
|
}
|
|
|
|
private function needsReview(string $state, ?CarbonInterface $keyDate): bool
|
|
{
|
|
if (! in_array($state, [WorkspaceSubscription::STATE_TRIAL, WorkspaceSubscription::STATE_CANCEL_AT_PERIOD_END], true)) {
|
|
return false;
|
|
}
|
|
|
|
return $keyDate instanceof CarbonInterface && $keyDate->isPast();
|
|
}
|
|
} |