@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Enums\LicenseStatus;
|
||||
use App\Models\BillingOrder;
|
||||
use App\Models\DeploymentInstallation;
|
||||
use App\Models\License;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class BillingService
|
||||
{
|
||||
public function activate(BillingOrder $order, ?int $issuerId = null): License
|
||||
{
|
||||
return DB::transaction(function () use ($order, $issuerId): License {
|
||||
$order = BillingOrder::withoutGlobalScope('tenant')->lockForUpdate()->findOrFail($order->id);
|
||||
if ($order->status === 'paid' && $order->license) {
|
||||
return $order->license;
|
||||
}
|
||||
$plan = config("billing.plans.{$order->plan_code}");
|
||||
abort_unless($plan, 422, 'Paket lisensi tidak tersedia.');
|
||||
$installation = DeploymentInstallation::withoutGlobalScope('tenant')->where('tenant_id', $order->tenant_id)->firstOrFail();
|
||||
$current = License::withoutGlobalScope('tenant')->where('tenant_id', $order->tenant_id)
|
||||
->whereIn('status', [LicenseStatus::Active->value, LicenseStatus::Grace->value])->latest('created_at')->first();
|
||||
$startsAt = $current && $current->plan !== 'cloud_free' && $current->expires_at->isFuture() ? $current->expires_at : now();
|
||||
$current?->update(['status' => LicenseStatus::Revoked]);
|
||||
$license = License::withoutGlobalScope('tenant')->create([
|
||||
'tenant_id' => $order->tenant_id, 'deployment_installation_id' => $installation->id,
|
||||
'license_key_hash' => hash('sha256', random_bytes(32)), 'plan' => $order->plan_code,
|
||||
'status' => LicenseStatus::Active, 'max_devices' => $plan['max_devices'], 'max_users' => $plan['max_users'],
|
||||
'features' => ['device_access_management'], 'starts_at' => $startsAt, 'expires_at' => $startsAt->addMonth(),
|
||||
'grace_until' => $startsAt->addMonth()->addDays(7), 'issued_by' => $issuerId,
|
||||
]);
|
||||
$order->update(['license_id' => $license->id, 'status' => 'paid', 'paid_at' => now()]);
|
||||
|
||||
return $license;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Enums\LicenseStatus;
|
||||
use App\Models\DeploymentInstallation;
|
||||
use App\Models\Device;
|
||||
use App\Models\License;
|
||||
use App\Models\Tenant;
|
||||
use App\Models\User;
|
||||
|
||||
class LicenseEntitlementService
|
||||
{
|
||||
public function allowsUse(int $tenantId): bool
|
||||
{
|
||||
$license = $this->current($tenantId);
|
||||
|
||||
return $license !== null && ($license->grace_until ?? $license->expires_at)->isFuture();
|
||||
}
|
||||
|
||||
public function provisionInitial(Tenant $tenant, DeploymentInstallation $installation, string $planCode, bool $activate = false, ?int $issuerId = null): License
|
||||
{
|
||||
$plan = config("billing.plans.{$planCode}");
|
||||
abort_unless($plan && $plan['deployment'] === $installation->deployment_type->value, 422, 'Paket tidak sesuai deployment tenant.');
|
||||
$isFree = $plan['price'] === 0;
|
||||
$startsAt = now();
|
||||
|
||||
return License::withoutGlobalScope('tenant')->create([
|
||||
'tenant_id' => $tenant->id, 'deployment_installation_id' => $installation->id,
|
||||
'license_key_hash' => hash('sha256', random_bytes(32)), 'plan' => $planCode,
|
||||
'status' => $activate || $isFree ? LicenseStatus::Active : LicenseStatus::Pending,
|
||||
'max_devices' => $plan['max_devices'], 'max_users' => $plan['max_users'], 'features' => ['device_access_management'],
|
||||
'starts_at' => $startsAt, 'expires_at' => $isFree ? $startsAt->copy()->addYears(10) : $startsAt->copy()->addMonth(),
|
||||
'grace_until' => $isFree ? null : $startsAt->copy()->addMonth()->addDays(7),
|
||||
'issued_by' => $issuerId,
|
||||
]);
|
||||
}
|
||||
|
||||
public function current(int $tenantId): ?License
|
||||
{
|
||||
$license = License::withoutGlobalScope('tenant')->where('tenant_id', $tenantId)
|
||||
->whereIn('status', [LicenseStatus::Active->value, LicenseStatus::Grace->value])
|
||||
->latest('created_at')->first();
|
||||
if ($license) {
|
||||
return $license;
|
||||
}
|
||||
if (License::withoutGlobalScope('tenant')->where('tenant_id', $tenantId)->exists()) {
|
||||
return null;
|
||||
}
|
||||
$installation = DeploymentInstallation::withoutGlobalScope('tenant')->where('tenant_id', $tenantId)->first();
|
||||
if (! $installation || $installation->deployment_type->value !== 'managed_cloud') {
|
||||
return null;
|
||||
}
|
||||
$plan = config('billing.plans.cloud_free');
|
||||
|
||||
return License::withoutGlobalScope('tenant')->create([
|
||||
'tenant_id' => $tenantId, 'deployment_installation_id' => $installation->id,
|
||||
'license_key_hash' => hash('sha256', random_bytes(32)), 'plan' => 'cloud_free',
|
||||
'status' => LicenseStatus::Active, 'max_devices' => $plan['max_devices'], 'max_users' => $plan['max_users'],
|
||||
'features' => ['device_access_management'], 'starts_at' => now(), 'expires_at' => now()->addYears(10),
|
||||
]);
|
||||
}
|
||||
|
||||
public function assertCanAddDevice(int $tenantId): void
|
||||
{
|
||||
$license = $this->current($tenantId);
|
||||
abort_unless($license, 402, 'Lisensi tenant belum aktif.');
|
||||
abort_if($license->max_devices !== null && Device::withoutGlobalScope('tenant')->where('tenant_id', $tenantId)->count() >= $license->max_devices, 422, 'Batas perangkat paket tercapai. Upgrade paket melalui menu Lisensi & Tagihan.');
|
||||
}
|
||||
|
||||
public function assertCanAddUser(int $tenantId): void
|
||||
{
|
||||
$license = $this->current($tenantId);
|
||||
abort_unless($license, 402, 'Lisensi tenant belum aktif.');
|
||||
abort_if($license->max_users !== null && User::withoutGlobalScope('tenant')->where('tenant_id', $tenantId)->count() >= $license->max_users, 422, 'Batas user aplikasi paket tercapai. Upgrade paket melalui menu Lisensi & Tagihan.');
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@ class TenantProvisioningService
|
||||
TenantDevicePolicy::create(['tenant_id' => $tenant->id]);
|
||||
app(TenantEnvelopeEncryption::class)->ensureKey($tenant->id);
|
||||
$deploymentType = $data['deployment_type'] ?? 'managed_cloud';
|
||||
DeploymentInstallation::create([
|
||||
$installation = DeploymentInstallation::create([
|
||||
'tenant_id' => $tenant->id,
|
||||
'name' => $deploymentType === 'self_hosted' ? 'Server Tenant' : 'RADIQ Managed Cloud',
|
||||
'deployment_type' => $deploymentType,
|
||||
@@ -38,6 +38,14 @@ class TenantProvisioningService
|
||||
'status' => $deploymentType === 'managed_cloud' ? 'active' : 'pending',
|
||||
'activated_at' => $deploymentType === 'managed_cloud' ? now() : null,
|
||||
]);
|
||||
$defaultPlan = $deploymentType === 'managed_cloud' ? 'cloud_free' : 'local_micro';
|
||||
app(LicenseEntitlementService::class)->provisionInitial(
|
||||
$tenant,
|
||||
$installation,
|
||||
$data['plan_code'] ?? $defaultPlan,
|
||||
(bool) ($data['activate_license'] ?? true),
|
||||
$data['issued_by'] ?? null,
|
||||
);
|
||||
|
||||
$this->context->set($tenant->id);
|
||||
setPermissionsTeamId($tenant->id);
|
||||
@@ -52,7 +60,7 @@ class TenantProvisioningService
|
||||
'password' => $data['owner_password'],
|
||||
'is_active' => true,
|
||||
]);
|
||||
$owner->forceFill(['email_verified_at' => now()])->save();
|
||||
$owner->forceFill(['email_verified_at' => ($data['email_verified'] ?? true) ? now() : null])->save();
|
||||
$owner->assignRole($roles[SystemRole::TenantAdmin->value]);
|
||||
} finally {
|
||||
$this->context->clear();
|
||||
|
||||
Reference in New Issue
Block a user